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