1 /* 2 * Copyright (c) 1987,1997, Prentice Hall 3 * All rights reserved. 4 * 5 * Redistribution and use of the MINIX operating system in source and 6 * binary forms, with or without modification, are permitted provided 7 * that the following conditions are met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above 13 * copyright notice, this list of conditions and the following 14 * disclaimer in the documentation and/or other materials provided 15 * with the distribution. 16 * 17 * * Neither the name of Prentice Hall nor the names of the software 18 * authors or contributors may be used to endorse or promote 19 * products derived from this software without specific prior 20 * written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, AUTHORS, AND 23 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 24 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 25 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 * IN NO EVENT SHALL PRENTICE HALL OR ANY AUTHORS OR CONTRIBUTORS BE 27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 30 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 31 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 32 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 33 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 * 35 * [original code from minix codebase] 36 * $DragonFly: src/bin/mined/mined2.c,v 1.6 2005/11/06 11:44:02 swildner Exp $* 37 */ 38 /* 39 * Part 2 of the mined editor. 40 */ 41 42 /* ======================================================================== * 43 * Move Commands * 44 * ======================================================================== */ 45 46 #include "mined.h" 47 #include <signal.h> 48 #include <string.h> 49 50 /* 51 * Move one line up. 52 */ 53 void 54 UP(int u __unused) 55 { 56 if (y == 0) { /* Top line of screen. Scroll one line */ 57 reverse_scroll(); 58 move_to(x, y); 59 } 60 else /* Move to previous line */ 61 move_to(x, y - 1); 62 } 63 64 static const char *help_string= 65 " Mined (Minix Editor), DragonFly version.\n" 66 "------------------------+-------------------------------+---------------------\n" 67 " CURSOR MOTION | EDITING | MISC\n" 68 " Up | ^N Delete next word | ^L Erase & redraw\n" 69 " Down cursor keys | ^P Delete prev. word | screen\n" 70 " Left | ^T Delete to EOL | ^\\ Abort current\n" 71 " Right +-------------------------------+ operation\n" 72 " ^A start of line | BLOCKS | Esc repeat last\n" 73 " ^E end of line | ^@ Set mark | cmd # times\n" 74 " ^^ screen top | ^K Delete mark <--> cursor | F2 file status\n" 75 " ^_ screen bottom | ^C Save mark <--> cursor +=====================\n" 76 " ^F word fwd. | ^Y Insert the contents of | ^X EXIT\n" 77 " ^B word back | the save file at cursor | ^S run shell\n" 78 "------------------------+ ^Q Insert the contents of +=====================\n" 79 " SCREEN MOTION | the save file into new | SEARCH & REPLACE\n" 80 " Home file top | file | F3 fwd. search\n" 81 " End file bottom +-------------------------------+ SF3 bck. search\n" 82 " PgUp page up | FILES | F4 Global replace\n" 83 " PgD page down | ^G Insert a file at cursor | SF4 Line replace\n" 84 " ^D rev. scroll | ^V Visit another file +---------------------\n" 85 " ^U fwd. scroll | ^W Write current file | F1 HELP\n" 86 " ^] goto line # | |\n" 87 "------------------------+-------------------------------+---------------------\n" 88 "Press any key to continue..."; 89 /* 90 * Help 91 */ 92 void 93 HLP(int u __unused) 94 { 95 char c; 96 97 string_print(enter_string); 98 string_print(help_string); 99 flush(); 100 c=getchar(); 101 RD(0); 102 return; 103 } 104 105 void 106 ST(int u __unused) 107 { 108 raw_mode(OFF); 109 kill(getpid(), SIGTSTP); 110 raw_mode(ON); 111 RD(0); 112 } 113 114 /* 115 * Move one line down. 116 */ 117 void 118 DN(int u __unused) 119 { 120 if (y == last_y) { /* Last line of screen. Scroll one line */ 121 if (bot_line->next == tail && bot_line->text[0] != '\n') { 122 dummy_line(); /* Create new empty line */ 123 DN(0); 124 return; 125 } 126 else { 127 forward_scroll(); 128 move_to(x, y); 129 } 130 } 131 else /* Move to next line */ 132 move_to(x, y + 1); 133 } 134 135 /* 136 * Move left one position. 137 */ 138 void 139 LF(int u __unused) 140 { 141 if (x == 0 && get_shift(cur_line->shift_count) == 0) {/* Begin of line */ 142 if (cur_line->prev != header) { 143 UP(0); /* Move one line up */ 144 move_to(LINE_END, y); 145 } 146 } 147 else 148 move_to(x - 1, y); 149 } 150 151 /* 152 * Move right one position. 153 */ 154 void 155 RT(int u __unused) 156 { 157 if (*cur_text == '\n') { 158 if (cur_line->next != tail) { /* Last char of file */ 159 DN(0); /* Move one line down */ 160 move_to(LINE_START, y); 161 } 162 } 163 else 164 move_to(x + 1, y); 165 } 166 167 /* 168 * Move to coordinates [0, 0] on screen. 169 */ 170 void 171 HIGH(int u __unused) 172 { 173 move_to(0, 0); 174 } 175 176 /* 177 * Move to coordinates [0, YMAX] on screen. 178 */ 179 void 180 LOW(int u __unused) 181 { 182 move_to(0, last_y); 183 } 184 185 /* 186 * Move to begin of line. 187 */ 188 void 189 BL(int u __unused) 190 { 191 move_to(LINE_START, y); 192 } 193 194 /* 195 * Move to end of line. 196 */ 197 void 198 EL(int u __unused) 199 { 200 move_to(LINE_END, y); 201 } 202 203 /* 204 * GOTO() prompts for a linenumber and moves to that line. 205 */ 206 void 207 GOTO(int u __unused) 208 { 209 int number; 210 LINE *line; 211 212 if (get_number("Please enter line number.", &number) == ERRORS) 213 return; 214 215 if (number <= 0 || (line = proceed(header->next, number - 1)) == tail) 216 error("Illegal line number: ", num_out((long) number)); 217 else 218 move_to(x, find_y(line)); 219 } 220 221 /* 222 * Scroll forward one page or to eof, whatever comes first. (Bot_line becomes 223 * top_line of display.) Try to leave the cursor on the same line. If this is 224 * not possible, leave cursor on the line halfway the page. 225 */ 226 void 227 PD(int u __unused) 228 { 229 int i; 230 231 for (i = 0; i < screenmax; i++) 232 if (forward_scroll() == ERRORS) 233 break; /* EOF reached */ 234 if (y - i < 0) /* Line no longer on screen */ 235 move_to(0, screenmax >> 1); 236 else 237 move_to(0, y - i); 238 } 239 240 241 /* 242 * Scroll backwards one page or to top of file, whatever comes first. (Top_line 243 * becomes bot_line of display). The very bottom line (YMAX) is always blank. 244 * Try to leave the cursor on the same line. If this is not possible, leave 245 * cursor on the line halfway the page. 246 */ 247 void 248 PU(int u __unused) 249 { 250 int i; 251 252 for (i = 0; i < screenmax; i++) 253 if (reverse_scroll() == ERRORS) 254 break; /* Top of file reached */ 255 set_cursor(0, ymax); /* Erase very bottom line */ 256 #ifdef UNIX 257 tputs(CE, 0, _putchar); 258 #else 259 string_print(blank_line); 260 #endif /* UNIX */ 261 if (y + i > screenmax) /* line no longer on screen */ 262 move_to(0, screenmax >> 1); 263 else 264 move_to(0, y + i); 265 } 266 267 /* 268 * Go to top of file, scrolling if possible, else redrawing screen. 269 */ 270 void 271 HO(int u __unused) 272 { 273 if (proceed(top_line, -screenmax) == header) 274 PU(0); /* It fits. Let PU do it */ 275 else { 276 reset(header->next, 0);/* Reset top_line, etc. */ 277 RD(0); /* Display full page */ 278 } 279 move_to(LINE_START, 0); 280 } 281 282 /* 283 * Go to last line of file, scrolling if possible, else redrawing screen 284 */ 285 void 286 EF(int u __unused) 287 { 288 if (tail->prev->text[0] != '\n') 289 dummy_line(); 290 if (proceed(bot_line, screenmax) == tail) 291 PD(0); /* It fits. Let PD do it */ 292 else { 293 reset(proceed(tail->prev, -screenmax), screenmax); 294 RD(0); /* Display full page */ 295 } 296 move_to(LINE_START, last_y); 297 } 298 299 /* 300 * Scroll one line up. Leave the cursor on the same line (if possible). 301 */ 302 void 303 SU(int u __unused) 304 { 305 if (top_line->prev == header) /* Top of file. Can't scroll */ 306 return; 307 308 reverse_scroll(); 309 set_cursor(0, ymax); /* Erase very bottom line */ 310 #ifdef UNIX 311 tputs(CE, 0, _putchar); 312 #else 313 string_print(blank_line); 314 #endif /* UNIX */ 315 move_to(x, (y == screenmax) ? screenmax : y + 1); 316 } 317 318 /* 319 * Scroll one line down. Leave the cursor on the same line (if possible). 320 */ 321 void 322 SD(int u __unused) 323 { 324 if (forward_scroll() != ERRORS) 325 move_to(x, (y == 0) ? 0 : y - 1); 326 else 327 set_cursor(x, y); 328 } 329 330 /* 331 * Perform a forward scroll. It returns ERRORS if we're at the last line of the 332 * file. 333 */ 334 int 335 forward_scroll(void) 336 { 337 if (bot_line->next == tail) /* Last line of file. No dice */ 338 return ERRORS; 339 top_line = top_line->next; 340 bot_line = bot_line->next; 341 cur_line = cur_line->next; 342 set_cursor(0, ymax); 343 line_print(bot_line); 344 345 return FINE; 346 } 347 348 /* 349 * Perform a backwards scroll. It returns ERRORS if we're at the first line 350 * of the file. 351 */ 352 int 353 reverse_scroll(void) 354 { 355 if (top_line->prev == header) 356 return ERRORS; /* Top of file. Can't scroll */ 357 358 if (last_y != screenmax) /* Reset last_y if necessary */ 359 last_y++; 360 else 361 bot_line = bot_line->prev; /* Else adjust bot_line */ 362 top_line = top_line->prev; 363 cur_line = cur_line->prev; 364 365 /* Perform the scroll */ 366 set_cursor(0, 0); 367 #ifdef UNIX 368 tputs(AL, 0, _putchar); 369 #else 370 string_print(rev_scroll); 371 #endif /* UNIX */ 372 set_cursor(0, 0); 373 line_print(top_line); 374 375 return FINE; 376 } 377 378 /* 379 * A word is defined as a number of non-blank characters separated by tabs 380 * spaces or linefeeds. 381 */ 382 383 /* 384 * MP() moves to the start of the previous word. A word is defined as a 385 * number of non-blank characters separated by tabs spaces or linefeeds. 386 */ 387 void 388 MP(int u __unused) 389 { 390 move_previous_word(NO_DELETE); 391 } 392 393 void 394 move_previous_word(FLAG remove) 395 { 396 char *begin_line; 397 char *textp; 398 char start_char = *cur_text; 399 char *start_pos = cur_text; 400 401 /* Fist check if we're at the beginning of line. */ 402 if (cur_text == cur_line->text) { 403 if (cur_line->prev == header) 404 return; 405 start_char = '\0'; 406 } 407 408 LF(0); 409 410 begin_line = cur_line->text; 411 textp = cur_text; 412 413 /* Check if we're in the middle of a word. */ 414 if (!alpha(*textp) || !alpha(start_char)) { 415 while (textp != begin_line && (white_space(*textp) || *textp == '\n')) 416 textp--; 417 } 418 419 /* Now we're at the end of previous word. Skip non-blanks until a blank comes */ 420 while (textp != begin_line && alpha(*textp)) 421 textp--; 422 423 /* Go to the next char if we're not at the beginning of the line */ 424 if (textp != begin_line && *textp != '\n') 425 textp++; 426 427 /* Find the x-coordinate of this address, and move to it */ 428 move_address(textp); 429 if (remove == DELETE) 430 delete(cur_line, textp, cur_line, start_pos); 431 } 432 433 /* 434 * MN() moves to the start of the next word. A word is defined as a number of 435 * non-blank characters separated by tabs spaces or linefeeds. Always keep in 436 * mind that the pointer shouldn't pass the '\n'. 437 */ 438 void 439 MN(int u __unused) 440 { 441 move_next_word(NO_DELETE); 442 } 443 444 void 445 move_next_word(FLAG remove) 446 { 447 char *textp = cur_text; 448 449 /* Move to the end of the current word. */ 450 while (*textp != '\n' && alpha(*textp)) 451 textp++; 452 453 /* Skip all white spaces */ 454 while (*textp != '\n' && white_space(*textp)) 455 textp++; 456 /* If we're deleting. delete the text in between */ 457 if (remove == DELETE) { 458 delete(cur_line, cur_text, cur_line, textp); 459 return; 460 } 461 462 /* If we're at end of line. move to the first word on the next line. */ 463 if (*textp == '\n' && cur_line->next != tail) { 464 DN(0); 465 move_to(LINE_START, y); 466 textp = cur_text; 467 while (*textp != '\n' && white_space(*textp)) 468 textp++; 469 } 470 move_address(textp); 471 } 472 473 /* ======================================================================== * 474 * Modify Commands * 475 * ======================================================================== */ 476 477 /* 478 * DCC deletes the character under the cursor. If this character is a '\n' the 479 * current line is joined with the next one. 480 * If this character is the only character of the line, the current line will 481 * be deleted. 482 */ 483 void 484 DCC(int u __unused) 485 { 486 if (*cur_text == '\n') 487 delete(cur_line,cur_text, cur_line->next,cur_line->next->text); 488 else 489 delete(cur_line, cur_text, cur_line, cur_text + 1); 490 } 491 492 /* 493 * DPC deletes the character on the left side of the cursor. If the cursor is 494 * at the beginning of the line, the last character if the previous line is 495 * deleted. 496 */ 497 void 498 DPC(int u __unused) 499 { 500 if (x == 0 && cur_line->prev == header) 501 return; /* Top of file */ 502 503 LF(0); /* Move one left */ 504 DCC(0); /* Delete character under cursor */ 505 } 506 507 /* 508 * DLN deletes all characters until the end of the line. If the current 509 * character is a '\n', then delete that char. 510 */ 511 void 512 DLN(int u __unused) 513 { 514 if (*cur_text == '\n') 515 DCC(0); 516 else 517 delete(cur_line, cur_text, cur_line, cur_text + length_of(cur_text) -1); 518 } 519 520 /* 521 * DNW() deletes the next word (as described in MN()) 522 */ 523 void 524 DNW(int u __unused) 525 { 526 if (*cur_text == '\n') 527 DCC(0); 528 else 529 move_next_word(DELETE); 530 } 531 532 /* 533 * DPW() deletes the next word (as described in MP()) 534 */ 535 void 536 DPW(int u __unused) 537 { 538 if (cur_text == cur_line->text) 539 DPC(0); 540 else 541 move_previous_word(DELETE); 542 } 543 544 /* 545 * Insert character `character' at current location. 546 */ 547 void 548 S(int character) 549 { 550 static char buffer[2]; 551 552 buffer[0] = character; 553 /* Insert the character */ 554 if (insert(cur_line, cur_text, buffer) == ERRORS) 555 return; 556 557 /* Fix screen */ 558 if (character == '\n') { 559 set_cursor(0, y); 560 if (y == screenmax) { /* Can't use display */ 561 line_print(cur_line); 562 forward_scroll(); 563 } 564 else { 565 reset(top_line, y); /* Reset pointers */ 566 display(0, y, cur_line, last_y - y); 567 } 568 move_to(0, (y == screenmax) ? y : y + 1); 569 } 570 else if (x + 1 == XBREAK)/* If line must be shifted, just call move_to*/ 571 move_to(x + 1, y); 572 else { /* else display rest of line */ 573 put_line(cur_line, x, FALSE); 574 move_to(x + 1, y); 575 } 576 } 577 578 /* 579 * CTL inserts a control-char at the current location. A message that this 580 * function is called is displayed at the status line. 581 */ 582 void 583 CTL(int u __unused) 584 { 585 char ctrl; 586 587 status_line("Enter control character.", NIL_PTR); 588 if ((ctrl = getchar()) >= '\01' && ctrl <= '\037') { 589 S(ctrl); /* Insert the char */ 590 clear_status(); 591 } 592 else 593 error ("Unknown control character", NIL_PTR); 594 } 595 596 /* 597 * LIB insert a line at the current position and moves back to the end of 598 * the previous line. 599 */ 600 void 601 LIB(int u __unused) 602 { 603 S('\n'); /* Insert the line */ 604 UP(0); /* Move one line up */ 605 move_to(LINE_END, y); /* Move to end of this line */ 606 } 607 608 /* 609 * Line_insert() inserts a new line with text pointed to by `string'. 610 * It returns the address of the new line. 611 */ 612 LINE * 613 line_insert(LINE *line, const char *string, int len) 614 { 615 LINE *new_line; 616 617 /* Allocate space for LINE structure and text */ 618 new_line = install_line(string, len); 619 620 /* Install the line into the double linked list */ 621 new_line->prev = line; 622 new_line->next = line->next; 623 line->next = new_line; 624 new_line->next->prev = new_line; 625 626 /* Increment nlines */ 627 nlines++; 628 629 return new_line; 630 } 631 632 /* 633 * Insert() insert the string `string' at the given line and location. 634 */ 635 int 636 insert(LINE *line, char *location, char *string) 637 { 638 char *bufp = text_buffer; /* Buffer for building line */ 639 char *textp = line->text; 640 641 if (length_of(textp) + length_of(string) >= MAX_CHARS) { 642 error("Line too long", NIL_PTR); 643 return ERRORS; 644 } 645 646 modified = TRUE; /* File has been modified */ 647 648 /* Copy part of line until `location' has been reached */ 649 while (textp != location) 650 *bufp++ = *textp++; 651 652 /* Insert string at this location */ 653 while (*string != '\0') 654 *bufp++ = *string++; 655 *bufp = '\0'; 656 657 if (*(string - 1) == '\n') /* Insert a new line */ 658 line_insert(line, location, length_of(location)); 659 else /* Append last part of line */ 660 copy_string(bufp, location); 661 662 /* Install the new text in this line */ 663 free_space(line->text); 664 line->text = alloc(length_of(text_buffer) + 1); 665 copy_string(line->text, text_buffer); 666 667 return FINE; 668 } 669 670 /* 671 * Line_delete() deletes the argument line out of the line list. The pointer to 672 * the next line is returned. 673 */ 674 LINE * 675 line_delete(LINE *line) 676 { 677 LINE *next_line = line->next; 678 679 /* Delete the line */ 680 line->prev->next = line->next; 681 line->next->prev = line->prev; 682 683 /* Free allocated space */ 684 free_space(line->text); 685 free_space((char*)line); 686 687 /* Decrement nlines */ 688 nlines--; 689 690 return next_line; 691 } 692 693 /* 694 * Delete() deletes all the characters (including newlines) between the 695 * startposition and endposition and fixes the screen accordingly. It 696 * returns the number of lines deleted. 697 */ 698 void 699 delete(LINE *start_line, char *start_textp, 700 LINE *end_line, char *end_textp) 701 { 702 char *textp = start_line->text; 703 char *bufp = text_buffer; /* Storage for new line->text */ 704 LINE *line, *stop; 705 int line_cnt = 0; /* Nr of lines deleted */ 706 int count = 0; 707 int shift = 0; /* Used in shift calculation */ 708 int nx = x; 709 710 modified = TRUE; /* File has been modified */ 711 712 /* Set up new line. Copy first part of start line until start_position. */ 713 while (textp < start_textp) { 714 *bufp++ = *textp++; 715 count++; 716 } 717 718 /* Check if line doesn't exceed MAX_CHARS */ 719 if (count + length_of(end_textp) >= MAX_CHARS) { 720 error("Line too long", NIL_PTR); 721 return; 722 } 723 724 /* Copy last part of end_line if end_line is not tail */ 725 copy_string(bufp, (end_textp != NIL_PTR) ? end_textp : "\n"); 726 727 /* Delete all lines between start and end_position (including end_line) */ 728 line = start_line->next; 729 stop = end_line->next; 730 while (line != stop && line != tail) { 731 line = line_delete(line); 732 line_cnt++; 733 } 734 735 /* Check if last line of file should be deleted */ 736 if (end_textp == NIL_PTR && length_of(start_line->text) == 1 && nlines > 1) { 737 start_line = start_line->prev; 738 line_delete(start_line->next); 739 line_cnt++; 740 } 741 else { /* Install new text */ 742 free_space(start_line->text); 743 start_line->text = alloc(length_of(text_buffer) + 1); 744 copy_string(start_line->text, text_buffer); 745 } 746 747 /* Fix screen. First check if line is shifted. Perhaps we should shift it back*/ 748 if (get_shift(start_line->shift_count)) { 749 shift = (XBREAK - count_chars(start_line)) / SHIFT_SIZE; 750 if (shift > 0) { /* Shift line `shift' back */ 751 if (shift >= get_shift(start_line->shift_count)) 752 start_line->shift_count = 0; 753 else 754 start_line->shift_count -= shift; 755 nx += shift * SHIFT_SIZE;/* Reset x value */ 756 } 757 } 758 759 if (line_cnt == 0) { /* Check if only one line changed */ 760 if (shift > 0) { /* Reprint whole line */ 761 set_cursor(0, y); 762 line_print(start_line); 763 } 764 else { /* Just display last part of line */ 765 set_cursor(x, y); 766 put_line(start_line, x, TRUE); 767 } 768 move_to(nx, y); /* Reset cur_text */ 769 return; 770 } 771 772 shift = last_y; /* Save value */ 773 reset(top_line, y); 774 display(0, y, start_line, shift - y); 775 move_to((line_cnt == 1) ? nx : 0, y); 776 } 777 778 /* ======================================================================== * 779 * Yank Commands * 780 * ======================================================================== */ 781 782 LINE *mark_line; /* For marking position. */ 783 char *mark_text; 784 int lines_saved; /* Nr of lines in buffer */ 785 786 /* 787 * PT() inserts the buffer at the current location. 788 */ 789 void 790 PT(int u __unused) 791 { 792 int fd; /* File descriptor for buffer */ 793 794 if ((fd = scratch_file(READ)) == ERRORS) 795 error("Buffer is empty.", NIL_PTR); 796 else { 797 file_insert(fd, FALSE);/* Insert the buffer */ 798 close(fd); 799 } 800 } 801 802 /* 803 * IF() prompt for a filename and inserts the file at the current location 804 * in the file. 805 */ 806 void 807 IF(int u __unused) 808 { 809 int fd; /* File descriptor of file */ 810 char name[LINE_LEN]; /* Buffer for file name */ 811 812 /* Get the file name */ 813 if (get_file("Get and insert file:", name) != FINE) 814 return; 815 816 if ((fd = open(name, 0)) < 0) 817 error("Cannot open ", name); 818 else { 819 file_insert(fd, TRUE); /* Insert the file */ 820 close(fd); 821 } 822 } 823 824 /* 825 * File_insert() inserts a an opened file (as given by filedescriptor fd) 826 * at the current location. 827 */ 828 void 829 file_insert(int fd, FLAG old_pos) 830 { 831 char line_buffer[MAX_CHARS]; /* Buffer for next line */ 832 LINE *line = cur_line; 833 int line_count = nlines; /* Nr of lines inserted */ 834 LINE *page = cur_line; 835 int ret = ERRORS; 836 837 /* Get the first piece of text (might be ended with a '\n') from fd */ 838 if (get_line(fd, line_buffer) == ERRORS) 839 return; /* Empty file */ 840 841 /* Insert this text at the current location. */ 842 if (insert(line, cur_text, line_buffer) == ERRORS) 843 return; 844 845 /* Repeat getting lines (and inserting lines) until EOF is reached */ 846 while ((ret = get_line(fd, line_buffer)) != ERRORS && ret != NO_LINE) 847 line = line_insert(line, line_buffer, ret); 848 849 if (ret == NO_LINE) { /* Last line read not ended by a '\n' */ 850 line = line->next; 851 insert(line, line->text, line_buffer); 852 } 853 854 /* Calculate nr of lines added */ 855 line_count = nlines - line_count; 856 857 /* Fix the screen */ 858 if (line_count == 0) { /* Only one line changed */ 859 set_cursor(0, y); 860 line_print(line); 861 move_to((old_pos == TRUE) ? x : x + length_of(line_buffer), y); 862 } 863 else { /* Several lines changed */ 864 reset(top_line, y); /* Reset pointers */ 865 while (page != line && page != bot_line->next) 866 page = page->next; 867 if (page != bot_line->next || old_pos == TRUE) 868 display(0, y, cur_line, screenmax - y); 869 if (old_pos == TRUE) 870 move_to(x, y); 871 else if (ret == NO_LINE) 872 move_to(length_of(line_buffer), find_y(line)); 873 else 874 move_to(0, find_y(line->next)); 875 } 876 877 /* If nr of added line >= REPORT, print the count */ 878 if (line_count >= REPORT) 879 status_line(num_out((long) line_count), " lines added."); 880 } 881 882 /* 883 * WB() writes the buffer (yank_file) into another file, which 884 * is prompted for. 885 */ 886 void 887 WB(int u __unused) 888 { 889 int new_fd; /* Filedescriptor to copy file */ 890 int yank_fd; /* Filedescriptor to buffer */ 891 int cnt; /* Count check for read/write */ 892 int ret = 0; /* Error check for write */ 893 char file[LINE_LEN]; /* Output file */ 894 895 /* Checkout the buffer */ 896 if ((yank_fd = scratch_file(READ)) == ERRORS) { 897 error("Buffer is empty.", NIL_PTR); 898 return; 899 } 900 901 /* Get file name */ 902 if (get_file("Write buffer to file:", file) != FINE) 903 return; 904 905 /* Creat the new file */ 906 if ((new_fd = creat(file, 0644)) < 0) { 907 error("Cannot create ", file); 908 return; 909 } 910 911 status_line("Writing ", file); 912 913 /* Copy buffer into file */ 914 while ((cnt = read(yank_fd, text_buffer, sizeof(text_buffer))) > 0) 915 if (write(new_fd, text_buffer, cnt) != cnt) { 916 bad_write(new_fd); 917 ret = ERRORS; 918 break; 919 } 920 921 /* Clean up open files and status_line */ 922 close(new_fd); 923 close(yank_fd); 924 925 if (ret != ERRORS) /* Bad write */ 926 file_status("Wrote", chars_saved, file, lines_saved, TRUE, FALSE); 927 } 928 929 /* 930 * MA sets mark_line (mark_text) to the current line (text pointer). 931 */ 932 void 933 MA(int u __unused) 934 { 935 mark_line = cur_line; 936 mark_text = cur_text; 937 status_line("Mark set", NIL_PTR); 938 } 939 940 /* 941 * YA() puts the text between the marked position and the current 942 * in the buffer. 943 */ 944 void 945 YA(int u __unused) 946 { 947 set_up(NO_DELETE); 948 } 949 950 /* 951 * DT() is essentially the same as YA(), but in DT() the text is deleted. 952 */ 953 void 954 DT(int u __unused) 955 { 956 set_up(DELETE); 957 } 958 959 /* 960 * Set_up is an interface to the actual yank. It calls checkmark () to check 961 * if the marked position is still valid. If it is, yank is called with the 962 * arguments in the right order. 963 * 964 * parameter 965 * remove: DELETE if text should be deleted 966 */ 967 void 968 set_up(FLAG remove) 969 { 970 switch (checkmark()) { 971 case NOT_VALID : 972 error("Mark not set.", NIL_PTR); 973 return; 974 case SMALLER : 975 yank(mark_line, mark_text, cur_line, cur_text, remove); 976 break; 977 case BIGGER : 978 yank(cur_line, cur_text, mark_line, mark_text, remove); 979 break; 980 case SAME : /* Ignore stupid behaviour */ 981 yank_status = EMPTY; 982 chars_saved = 0L; 983 status_line("0 characters saved in buffer.", NIL_PTR); 984 break; 985 } 986 } 987 988 /* 989 * Check_mark() checks if mark_line and mark_text are still valid pointers. If 990 * they are it returns SMALLER if the marked position is before the current, 991 * BIGGER if it isn't or SAME if somebody didn't get the point. 992 * NOT_VALID is returned when mark_line and/or mark_text are no longer valid. 993 * Legal() checks if mark_text is valid on the mark_line. 994 */ 995 FLAG 996 checkmark(void) 997 { 998 LINE *line; 999 FLAG cur_seen = FALSE; 1000 1001 /* Special case: check is mark_line and cur_line are the same. */ 1002 if (mark_line == cur_line) { 1003 if (mark_text == cur_text) /* Even same place */ 1004 return SAME; 1005 if (legal() == ERRORS) /* mark_text out of range */ 1006 return NOT_VALID; 1007 return (mark_text < cur_text) ? SMALLER : BIGGER; 1008 } 1009 1010 /* Start looking for mark_line in the line structure */ 1011 for (line = header->next; line != tail; line = line->next) { 1012 if (line == cur_line) 1013 cur_seen = TRUE; 1014 else if (line == mark_line) 1015 break; 1016 } 1017 1018 /* If we found mark_line (line != tail) check for legality of mark_text */ 1019 if (line == tail || legal() == ERRORS) 1020 return NOT_VALID; 1021 1022 /* cur_seen is TRUE if cur_line is before mark_line */ 1023 return (cur_seen == TRUE) ? BIGGER : SMALLER; 1024 } 1025 1026 /* 1027 * Legal() checks if mark_text is still a valid pointer. 1028 */ 1029 int 1030 legal(void) 1031 { 1032 char *textp = mark_line->text; 1033 1034 /* Locate mark_text on mark_line */ 1035 while (textp != mark_text && *textp++ != '\0') 1036 ; 1037 return (*textp == '\0') ? ERRORS : FINE; 1038 } 1039 1040 /* 1041 * Yank puts all the text between start_position and end_position into 1042 * the buffer. 1043 * The caller must check that the arguments to yank() are valid. (E.g. in 1044 * the right order) 1045 * 1046 * parameter 1047 * remove: DELETE if text should be deleted 1048 */ 1049 void 1050 yank(LINE *start_line, char *start_textp, LINE *end_line, char *end_textp, 1051 FLAG remove) 1052 { 1053 LINE *line = start_line; 1054 char *textp = start_textp; 1055 int fd; 1056 1057 /* Creat file to hold buffer */ 1058 if ((fd = scratch_file(WRITE)) == ERRORS) 1059 return; 1060 1061 chars_saved = 0L; 1062 lines_saved = 0; 1063 status_line("Saving text.", NIL_PTR); 1064 1065 /* Keep writing chars until the end_location is reached. */ 1066 while (textp != end_textp) { 1067 if (write_char(fd, *textp) == ERRORS) { 1068 close(fd); 1069 return; 1070 } 1071 if (*textp++ == '\n') { /* Move to the next line */ 1072 line = line->next; 1073 textp = line->text; 1074 lines_saved++; 1075 } 1076 chars_saved++; 1077 } 1078 1079 /* Flush the I/O buffer and close file */ 1080 if (flush_buffer(fd) == ERRORS) { 1081 close(fd); 1082 return; 1083 } 1084 close(fd); 1085 yank_status = VALID; 1086 1087 /* 1088 * Check if the text should be deleted as well. If it should, the following 1089 * hack is used to save a lot of code. First move back to the start_position. 1090 * (This might be the location we're on now!) and them delete the text. 1091 * It might be a bit confusing the first time somebody uses it. 1092 * Delete() will fix the screen. 1093 */ 1094 if (remove == DELETE) { 1095 move_to(find_x(start_line, start_textp), find_y(start_line)); 1096 delete(start_line, start_textp, end_line, end_textp); 1097 } 1098 1099 status_line(num_out(chars_saved), " characters saved in buffer."); 1100 } 1101 1102 /* 1103 * Scratch_file() creates a uniq file in /usr/tmp. If the file couldn't 1104 * be created other combinations of files are tried until a maximum 1105 * of MAXTRAILS times. After MAXTRAILS times, an error message is given 1106 * and ERRORS is returned. 1107 */ 1108 1109 #define MAXTRAILS 26 1110 1111 /* 1112 * parameter 1113 * mode: Can be READ or WRITE permission 1114 */ 1115 int 1116 scratch_file(FLAG mode) 1117 { 1118 static int trials = 0; /* Keep track of trails */ 1119 char *y_ptr, *n_ptr; 1120 int fd = ERRORS; /* Filedescriptor to buffer */ 1121 1122 /* If yank_status == NOT_VALID, scratch_file is called for the first time */ 1123 if (yank_status == NOT_VALID && mode == WRITE) { /* Create new file */ 1124 /* Generate file name. */ 1125 y_ptr = &yank_file[11]; 1126 n_ptr = num_out((long) getpid()); 1127 while ((*y_ptr = *n_ptr++) != '\0') 1128 y_ptr++; 1129 *y_ptr++ = 'a' + trials; 1130 *y_ptr = '\0'; 1131 /* Check file existence */ 1132 if (access(yank_file, 0) == 0 || (fd = creat(yank_file, 0644)) < 0) { 1133 if (trials++ >= MAXTRAILS) { 1134 error("Unable to creat scratchfile.", NIL_PTR); 1135 return ERRORS; 1136 } 1137 else 1138 return scratch_file(mode);/* Have another go */ 1139 } 1140 } 1141 else if ((mode == READ && (fd = open(yank_file, 0)) < 0) || 1142 (mode == WRITE && (fd = creat(yank_file, 0644)) < 0)) { 1143 yank_status = NOT_VALID; 1144 return ERRORS; 1145 } 1146 1147 clear_buffer(); 1148 return fd; 1149 } 1150 1151 /* ======================================================================== * 1152 * Search Routines * 1153 * ======================================================================== */ 1154 1155 /* 1156 * A regular expression consists of a sequence of: 1157 * 1. A normal character matching that character. 1158 * 2. A . matching any character. 1159 * 3. A ^ matching the begin of a line. 1160 * 4. A $ (as last character of the pattern) mathing the end of a line. 1161 * 5. A \<character> matching <character>. 1162 * 6. A number of characters enclosed in [] pairs matching any of these 1163 * characters. A list of characters can be indicated by a '-'. So 1164 * [a-z] matches any letter of the alphabet. If the first character 1165 * after the '[' is a '^' then the set is negated (matching none of 1166 * the characters). 1167 * A ']', '^' or '-' can be escaped by putting a '\' in front of it. 1168 * 7. If one of the expressions as described in 1-6 is followed by a 1169 * '*' than that expressions matches a sequence of 0 or more of 1170 * that expression. 1171 */ 1172 1173 char typed_expression[LINE_LEN]; /* Holds previous expr. */ 1174 1175 /* 1176 * SF searches forward for an expression. 1177 */ 1178 void 1179 SF(int u __unused) 1180 { 1181 search("Search forward:", FORWARD); 1182 } 1183 1184 /* 1185 * SF searches backwards for an expression. 1186 */ 1187 void 1188 SR(int u __unused) 1189 { 1190 search("Search reverse:", REVERSE); 1191 } 1192 1193 /* 1194 * Get_expression() prompts for an expression. If just a return is typed, the 1195 * old expression is used. If the expression changed, compile() is called and 1196 * the returning REGEX structure is returned. It returns NIL_REG upon error. 1197 * The save flag indicates whether the expression should be appended at the 1198 * message pointer. 1199 */ 1200 REGEX * 1201 get_expression(const char *message) 1202 { 1203 static REGEX program; /* Program of expression */ 1204 char exp_buf[LINE_LEN]; /* Buffer for new expr. */ 1205 1206 if (get_string(message, exp_buf, FALSE) == ERRORS) 1207 return NIL_REG; 1208 1209 if (exp_buf[0] == '\0' && typed_expression[0] == '\0') { 1210 error("No previous expression.", NIL_PTR); 1211 return NIL_REG; 1212 } 1213 1214 if (exp_buf[0] != '\0') { /* A new expr. is typed */ 1215 copy_string(typed_expression, exp_buf);/* Save expr. */ 1216 compile(exp_buf, &program); /* Compile new expression */ 1217 } 1218 1219 if (program.status == REG_ERROR) { /* Error during compiling */ 1220 error(program.result.err_mess, NIL_PTR); 1221 return NIL_REG; 1222 } 1223 return &program; 1224 } 1225 1226 /* 1227 * GR() a replaces all matches from the current position until the end 1228 * of the file. 1229 */ 1230 void 1231 GR(int u __unused) 1232 { 1233 change("Global replace:", VALID); 1234 } 1235 1236 /* 1237 * LR() replaces all matches on the current line. 1238 */ 1239 void 1240 LR(int u __unused) 1241 { 1242 change("Line replace:", NOT_VALID); 1243 } 1244 1245 /* 1246 * Change() prompts for an expression and a substitution pattern and changes 1247 * all matches of the expression into the substitution. change() start looking 1248 * for expressions at the current line and continues until the end of the file 1249 * if the FLAG file is VALID. 1250 * 1251 * parameter 1252 * message: Message to prompt for expression 1253 */ 1254 void 1255 change(const char *message, FLAG file) 1256 { 1257 char mess_buf[LINE_LEN]; /* Buffer to hold message */ 1258 char replacement[LINE_LEN]; /* Buffer to hold subst. pattern */ 1259 REGEX *program; /* Program resulting from compilation */ 1260 LINE *line = cur_line; 1261 char *textp; 1262 long lines = 0L; /* Nr of lines on which subs occurred */ 1263 long subs = 0L; /* Nr of subs made */ 1264 int page = y; /* Index to check if line is on screen*/ 1265 1266 /* Save message and get expression */ 1267 copy_string(mess_buf, message); 1268 if ((program = get_expression(mess_buf)) == NIL_REG) 1269 return; 1270 1271 /* Get substitution pattern */ 1272 build_string(mess_buf, "%s %s by:", mess_buf, typed_expression); 1273 if (get_string(mess_buf, replacement, FALSE) == ERRORS) 1274 return; 1275 1276 set_cursor(0, ymax); 1277 flush(); 1278 /* Substitute until end of file */ 1279 do { 1280 if (line_check(program, line->text, FORWARD)) { 1281 lines++; 1282 /* Repeat sub. on this line as long as we find a match*/ 1283 do { 1284 subs++; /* Increment subs */ 1285 if ((textp = substitute(line, program,replacement)) 1286 == NIL_PTR) 1287 return; /* Line too long */ 1288 } while ((program->status & BEGIN_LINE) != BEGIN_LINE && 1289 (program->status & END_LINE) != END_LINE && 1290 line_check(program, textp, FORWARD)); 1291 /* Check to see if we can print the result */ 1292 if (page <= screenmax) { 1293 set_cursor(0, page); 1294 line_print(line); 1295 } 1296 } 1297 if (page <= screenmax) 1298 page++; 1299 line = line->next; 1300 } while (line != tail && file == VALID && quit == FALSE); 1301 1302 copy_string(mess_buf, (quit == TRUE) ? "(Aborted) " : ""); 1303 /* Fix the status line */ 1304 if (subs == 0L && quit == FALSE) 1305 error("Pattern not found.", NIL_PTR); 1306 else if (lines >= REPORT || quit == TRUE) { 1307 build_string(mess_buf, "%s %D substitutions on %D lines.", mess_buf, 1308 subs, lines); 1309 status_line(mess_buf, NIL_PTR); 1310 } 1311 else if (file == NOT_VALID && subs >= REPORT) 1312 status_line(num_out(subs), " substitutions."); 1313 else 1314 clear_status(); 1315 move_to (x, y); 1316 } 1317 1318 /* 1319 * Substitute() replaces the match on this line by the substitute pattern 1320 * as indicated by the program. Every '&' in the replacement is replaced by 1321 * the original match. A \ in the replacement escapes the next character. 1322 * 1323 * parameter 1324 * replacement: Contains replacement pattern 1325 */ 1326 char * 1327 substitute(LINE *line, REGEX *program, char *replacement) 1328 { 1329 char *textp = text_buffer; 1330 char *subp = replacement; 1331 char *linep = line->text; 1332 char *amp; 1333 1334 modified = TRUE; 1335 1336 /* Copy part of line until the beginning of the match */ 1337 while (linep != program->start_ptr) 1338 *textp++ = *linep++; 1339 1340 /* 1341 * Replace the match by the substitution pattern. Each occurrence of '&' is 1342 * replaced by the original match. A \ escapes the next character. 1343 */ 1344 while (*subp != '\0' && textp < &text_buffer[MAX_CHARS]) { 1345 if (*subp == '&') { /* Replace the original match */ 1346 amp = program->start_ptr; 1347 while (amp < program->end_ptr && textp<&text_buffer[MAX_CHARS]) 1348 *textp++ = *amp++; 1349 subp++; 1350 } 1351 else { 1352 if (*subp == '\\' && *(subp + 1) != '\0') 1353 subp++; 1354 *textp++ = *subp++; 1355 } 1356 } 1357 1358 /* Check for line length not exceeding MAX_CHARS */ 1359 if (length_of(text_buffer) + length_of(program->end_ptr) >= MAX_CHARS) { 1360 error("Substitution result: line too big", NIL_PTR); 1361 return NIL_PTR; 1362 } 1363 1364 /* Append last part of line to the new build line */ 1365 copy_string(textp, program->end_ptr); 1366 1367 /* Free old line and install new one */ 1368 free_space(line->text); 1369 line->text = alloc(length_of(text_buffer) + 1); 1370 copy_string(line->text, text_buffer); 1371 1372 return(line->text + (textp - text_buffer)); 1373 } 1374 1375 /* 1376 * Search() calls get_expression to fetch the expression. If this went well, 1377 * the function match() is called which returns the line with the next match. 1378 * If this line is the NIL_LINE, it means that a match could not be found. 1379 * Find_x() and find_y() display the right page on the screen, and return 1380 * the right coordinates for x and y. These coordinates are passed to move_to() 1381 */ 1382 void 1383 search(const char *message, FLAG method) 1384 { 1385 REGEX *program; 1386 LINE *match_line; 1387 1388 /* Get the expression */ 1389 if ((program = get_expression(message)) == NIL_REG) 1390 return; 1391 1392 set_cursor(0, ymax); 1393 flush(); 1394 /* Find the match */ 1395 if ((match_line = match(program, cur_text, method)) == NIL_LINE) { 1396 if (quit == TRUE) 1397 status_line("Aborted", NIL_PTR); 1398 else 1399 status_line("Pattern not found.", NIL_PTR); 1400 return; 1401 } 1402 1403 move(0, program->start_ptr, find_y(match_line)); 1404 clear_status(); 1405 } 1406 1407 /* 1408 * find_y() checks if the matched line is on the current page. If it is, it 1409 * returns the new y coordinate, else it displays the correct page with the 1410 * matched line in the middle and returns the new y value; 1411 */ 1412 int 1413 find_y(LINE *match_line) 1414 { 1415 LINE *line; 1416 int count = 0; 1417 1418 /* Check if match_line is on the same page as currently displayed. */ 1419 for (line = top_line; line != match_line && line != bot_line->next; 1420 line = line->next) 1421 count++; 1422 if (line != bot_line->next) 1423 return count; 1424 1425 /* Display new page, with match_line in center. */ 1426 if ((line = proceed(match_line, -(screenmax >> 1))) == header) { 1427 /* Can't display in the middle. Make first line of file top_line */ 1428 count = 0; 1429 for (line = header->next; line != match_line; line = line->next) 1430 count++; 1431 line = header->next; 1432 } 1433 else /* New page is displayed. Set cursor to middle of page */ 1434 count = screenmax >> 1; 1435 1436 /* Reset pointers and redraw the screen */ 1437 reset(line, 0); 1438 RD(0); 1439 1440 return count; 1441 } 1442 1443 /* Opcodes for characters */ 1444 #define NORMAL 0x0200 1445 #define DOT 0x0400 1446 #define EOLN 0x0800 1447 #define STAR 0x1000 1448 #define BRACKET 0x2000 1449 #define NEGATE 0x0100 1450 #define DONE 0x4000 1451 1452 /* Mask for opcodes and characters */ 1453 #define LOW_BYTE 0x00FF 1454 #define HIGH_BYTE 0xFF00 1455 1456 /* Previous is the contents of the previous address (ptr) points to */ 1457 #define previous(ptr) (*((ptr) - 1)) 1458 1459 /* Buffer to store outcome of compilation */ 1460 int exp_buffer[BLOCK_SIZE]; 1461 1462 /* Errors often used */ 1463 static const char *too_long = "Regular expression too long"; 1464 1465 /* 1466 * Reg_error() is called by compile() is something went wrong. It set the 1467 * status of the structure to error, and assigns the error field of the union. 1468 */ 1469 #define reg_error(str) program->status = REG_ERROR, \ 1470 program->result.err_mess = (str) 1471 /* 1472 * Finished() is called when everything went right during compilation. It 1473 * allocates space for the expression, and copies the expression buffer into 1474 * this field. 1475 */ 1476 void 1477 finished(REGEX *program, int *last_exp) 1478 { 1479 int length = (last_exp - exp_buffer) * sizeof(int); 1480 1481 /* Allocate space */ 1482 program->result.expression = (int *) alloc(length); 1483 /* Copy expression. (expression consists of ints!) */ 1484 bcopy(exp_buffer, program->result.expression, length); 1485 } 1486 1487 /* 1488 * Compile compiles the pattern into a more comprehensible form and returns a 1489 * REGEX structure. If something went wrong, the status field of the structure 1490 * is set to REG_ERROR and an error message is set into the err_mess field of 1491 * the union. If all went well the expression is saved and the expression 1492 * pointer is set to the saved (and compiled) expression. 1493 * 1494 * parameter 1495 * pattern: Pointer to pattern 1496 */ 1497 void 1498 compile(char *pattern, REGEX *program) 1499 { 1500 int *expression = exp_buffer; 1501 int *prev_char; /* Pointer to previous compiled atom */ 1502 int *acct_field = NULL; /* Pointer to last BRACKET start */ 1503 FLAG negate; /* Negate flag for BRACKET */ 1504 char low_char; /* Index for chars in BRACKET */ 1505 char c; 1506 1507 /* Check for begin of line */ 1508 if (*pattern == '^') { 1509 program->status = BEGIN_LINE; 1510 pattern++; 1511 } 1512 else { 1513 program->status = 0; 1514 /* If the first character is a '*' we have to assign it here. */ 1515 if (*pattern == '*') { 1516 *expression++ = '*' + NORMAL; 1517 pattern++; 1518 } 1519 } 1520 1521 for (; ;) { 1522 switch (c = *pattern++) { 1523 case '.' : 1524 *expression++ = DOT; 1525 break; 1526 case '$' : 1527 /* 1528 * Only means EOLN if it is the last char of the pattern 1529 */ 1530 if (*pattern == '\0') { 1531 *expression++ = EOLN | DONE; 1532 program->status |= END_LINE; 1533 finished(program, expression); 1534 return; 1535 } 1536 else 1537 *expression++ = NORMAL + '$'; 1538 break; 1539 case '\0' : 1540 *expression++ = DONE; 1541 finished(program, expression); 1542 return; 1543 case '\\' : 1544 /* If last char, it must! mean a normal '\' */ 1545 if (*pattern == '\0') 1546 *expression++ = NORMAL + '\\'; 1547 else 1548 *expression++ = NORMAL + *pattern++; 1549 break; 1550 case '*' : 1551 /* 1552 * If the previous expression was a [] find out the 1553 * begin of the list, and adjust the opcode. 1554 */ 1555 prev_char = expression - 1; 1556 if (*prev_char & BRACKET) 1557 *(expression - (*acct_field & LOW_BYTE))|= STAR; 1558 else 1559 *prev_char |= STAR; 1560 break; 1561 case '[' : 1562 /* 1563 * First field in expression gives information about 1564 * the list. 1565 * The opcode consists of BRACKET and if necessary 1566 * NEGATE to indicate that the list should be negated 1567 * and/or STAR to indicate a number of sequence of this 1568 * list. 1569 * The lower byte contains the length of the list. 1570 */ 1571 acct_field = expression++; 1572 if (*pattern == '^') { /* List must be negated */ 1573 pattern++; 1574 negate = TRUE; 1575 } 1576 else 1577 negate = FALSE; 1578 while (*pattern != ']') { 1579 if (*pattern == '\0') { 1580 reg_error("Missing ]"); 1581 return; 1582 } 1583 if (*pattern == '\\') 1584 pattern++; 1585 *expression++ = *pattern++; 1586 if (*pattern == '-') { 1587 /* Make list of chars */ 1588 low_char = previous(pattern); 1589 pattern++; /* Skip '-' */ 1590 if (low_char++ > *pattern) { 1591 reg_error("Bad range in [a-z]"); 1592 return; 1593 } 1594 /* Build list */ 1595 while (low_char <= *pattern) 1596 *expression++ = low_char++; 1597 pattern++; 1598 } 1599 if (expression >= &exp_buffer[BLOCK_SIZE]) { 1600 reg_error(too_long); 1601 return; 1602 } 1603 } 1604 pattern++; /* Skip ']' */ 1605 /* Assign length of list in acct field */ 1606 if ((*acct_field = (expression - acct_field)) == 1) { 1607 reg_error("Empty []"); 1608 return; 1609 } 1610 /* Assign negate and bracket field */ 1611 *acct_field |= BRACKET; 1612 if (negate == TRUE) 1613 *acct_field |= NEGATE; 1614 /* 1615 * Add BRACKET to opcode of last char in field because 1616 * a '*' may be following the list. 1617 */ 1618 previous(expression) |= BRACKET; 1619 break; 1620 default : 1621 *expression++ = c + NORMAL; 1622 } 1623 if (expression == &exp_buffer[BLOCK_SIZE]) { 1624 reg_error(too_long); 1625 return; 1626 } 1627 } 1628 /* NOTREACHED */ 1629 } 1630 1631 /* 1632 * Match gets as argument the program, pointer to place in current line to 1633 * start from and the method to search for (either FORWARD or REVERSE). 1634 * Match() will look through the whole file until a match is found. 1635 * NIL_LINE is returned if no match could be found. 1636 */ 1637 LINE * 1638 match(REGEX *program, char *string, FLAG method) 1639 { 1640 LINE *line = cur_line; 1641 char old_char; /* For saving chars */ 1642 1643 /* Corrupted program */ 1644 if (program->status == REG_ERROR) 1645 return NIL_LINE; 1646 1647 /* Check part of text first */ 1648 if (!(program->status & BEGIN_LINE)) { 1649 if (method == FORWARD) { 1650 if (line_check(program, string + 1, method) == MATCH) 1651 return cur_line; /* Match found */ 1652 } 1653 else if (!(program->status & END_LINE)) { 1654 old_char = *string; /* Save char and */ 1655 *string = '\n'; /* Assign '\n' for line_check */ 1656 if (line_check(program, line->text, method) == MATCH) { 1657 *string = old_char; /* Restore char */ 1658 return cur_line; /* Found match */ 1659 } 1660 *string = old_char; /* No match, but restore char */ 1661 } 1662 } 1663 1664 /* No match in last (or first) part of line. Check out rest of file */ 1665 do { 1666 line = (method == FORWARD) ? line->next : line->prev; 1667 if (line->text == NIL_PTR) /* Header/tail */ 1668 continue; 1669 if (line_check(program, line->text, method) == MATCH) 1670 return line; 1671 } while (line != cur_line && quit == FALSE); 1672 1673 /* No match found. */ 1674 return NIL_LINE; 1675 } 1676 1677 /* 1678 * Line_check() checks the line (or rather string) for a match. Method 1679 * indicates FORWARD or REVERSE search. It scans through the whole string 1680 * until a match is found, or the end of the string is reached. 1681 */ 1682 int 1683 line_check(REGEX *program, char *string, FLAG method) 1684 { 1685 char *textp = string; 1686 1687 /* Assign start_ptr field. We might find a match right away! */ 1688 program->start_ptr = textp; 1689 1690 /* If the match must be anchored, just check the string. */ 1691 if (program->status & BEGIN_LINE) 1692 return check_string(program, string, NIL_INT); 1693 1694 if (method == REVERSE) { 1695 /* First move to the end of the string */ 1696 for (textp = string; *textp != '\n'; textp++) 1697 ; 1698 /* Start checking string until the begin of the string is met */ 1699 while (textp >= string) { 1700 program->start_ptr = textp; 1701 if (check_string(program, textp--, NIL_INT)) 1702 return MATCH; 1703 } 1704 } 1705 else { 1706 /* Move through the string until the end of is found */ 1707 while (quit == FALSE && *textp != '\0') { 1708 program->start_ptr = textp; 1709 if (check_string(program, textp, NIL_INT)) 1710 return MATCH; 1711 if (*textp == '\n') 1712 break; 1713 textp++; 1714 } 1715 } 1716 1717 return NO_MATCH; 1718 } 1719 1720 /* 1721 * Check() checks of a match can be found in the given string. Whenever a STAR 1722 * is found during matching, then the begin position of the string is marked 1723 * and the maximum number of matches is performed. Then the function star() 1724 * is called which starts to finish the match from this position of the string 1725 * (and expression). Check() return MATCH for a match, NO_MATCH is the string 1726 * couldn't be matched or REG_ERROR for an illegal opcode in expression. 1727 */ 1728 int 1729 check_string(REGEX *program, char *string, int *expression) 1730 { 1731 int opcode; /* Holds opcode of next expr. atom */ 1732 char c; /* Char that must be matched */ 1733 char *mark = NULL; /* For marking position */ 1734 int star_fl; /* A star has been born */ 1735 1736 if (expression == NIL_INT) 1737 expression = program->result.expression; 1738 1739 /* Loop until end of string or end of expression */ 1740 while (quit == FALSE && !(*expression & DONE) && 1741 *string != '\0' && *string != '\n') { 1742 c = *expression & LOW_BYTE; /* Extract match char */ 1743 opcode = *expression & HIGH_BYTE; /* Extract opcode */ 1744 if ((star_fl = (opcode & STAR)) != 0) { /* Check star occurrence */ 1745 opcode &= ~STAR; /* Strip opcode */ 1746 mark = string; /* Mark current position */ 1747 } 1748 expression++; /* Increment expr. */ 1749 switch (opcode) { 1750 case NORMAL : 1751 if (star_fl) 1752 while (*string++ == c) /* Skip all matches */ 1753 ; 1754 else if (*string++ != c) 1755 return NO_MATCH; 1756 break; 1757 case DOT : 1758 string++; 1759 if (star_fl) /* Skip to eoln */ 1760 while (*string != '\0' && *string++ != '\n') 1761 ; 1762 break; 1763 case NEGATE | BRACKET: 1764 case BRACKET : 1765 if (star_fl) 1766 while (in_list(expression, *string++, c, opcode) 1767 == MATCH) 1768 ; 1769 else if (in_list(expression, *string++, c, opcode) == NO_MATCH) 1770 return NO_MATCH; 1771 expression += c - 1; /* Add length of list */ 1772 break; 1773 default : 1774 panic("Corrupted program in check_string()"); 1775 } 1776 if (star_fl) 1777 return star(program, mark, string, expression); 1778 } 1779 if (*expression & DONE) { 1780 program->end_ptr = string; /* Match ends here */ 1781 /* 1782 * We might have found a match. The last thing to do is check 1783 * whether a '$' was given at the end of the expression, or 1784 * the match was found on a null string. (E.g. [a-z]* always 1785 * matches) unless a ^ or $ was included in the pattern. 1786 */ 1787 if ((*expression & EOLN) && *string != '\n' && *string != '\0') 1788 return NO_MATCH; 1789 if (string == program->start_ptr && !(program->status & BEGIN_LINE) 1790 && !(*expression & EOLN)) 1791 return NO_MATCH; 1792 return MATCH; 1793 } 1794 return NO_MATCH; 1795 } 1796 1797 /* 1798 * Star() calls check_string() to find out the longest match possible. 1799 * It searches backwards until the (in check_string()) marked position 1800 * is reached, or a match is found. 1801 */ 1802 int 1803 star(REGEX *program, char *end_position, char *string, int *expression) 1804 { 1805 do { 1806 string--; 1807 if (check_string(program, string, expression)) 1808 return MATCH; 1809 } while (string != end_position); 1810 1811 return NO_MATCH; 1812 } 1813 1814 /* 1815 * In_list() checks if the given character is in the list of []. If it is 1816 * it returns MATCH. if it isn't it returns NO_MATCH. These returns values 1817 * are reversed when the NEGATE field in the opcode is present. 1818 */ 1819 int 1820 in_list(int *list, char c, int list_length, int opcode) 1821 { 1822 if (c == '\0' || c == '\n') /* End of string, never matches */ 1823 return NO_MATCH; 1824 while (list_length-- > 1) { /* > 1, don't check acct_field */ 1825 if ((*list & LOW_BYTE) == c) 1826 return (opcode & NEGATE) ? NO_MATCH : MATCH; 1827 list++; 1828 } 1829 return (opcode & NEGATE) ? MATCH : NO_MATCH; 1830 } 1831 1832 /* 1833 * Dummy_line() adds an empty line at the end of the file. This is sometimes 1834 * useful in combination with the EF and DN command in combination with the 1835 * Yank command set. 1836 */ 1837 void 1838 dummy_line(void) 1839 { 1840 line_insert(tail->prev, "\n", 1); 1841 tail->prev->shift_count = DUMMY; 1842 if (last_y != screenmax) { 1843 last_y++; 1844 bot_line = bot_line->next; 1845 } 1846 } 1847