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