Pre-2.0 release: Sync with HAMMER 64 - NFS and cross-device link fixes.
[dragonfly.git] / bin / mined / mined2.c
blob3bbf4df136d8e97acd1ca20405b0e9b1405c149d
1 /*
2 * Copyright (c) 1987,1997, Prentice Hall
3 * All rights reserved.
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:
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
35 * [original code from minix codebase]
36 * $DragonFly: src/bin/mined/mined2.c,v 1.6 2005/11/06 11:44:02 swildner Exp $*
39 * Part 2 of the mined editor.
42 /* ======================================================================== *
43 * Move Commands *
44 * ======================================================================== */
46 #include "mined.h"
47 #include <signal.h>
48 #include <string.h>
51 * Move one line up.
53 void
54 UP(int u __unused)
56 if (y == 0) { /* Top line of screen. Scroll one line */
57 reverse_scroll();
58 move_to(x, y);
60 else /* Move to previous line */
61 move_to(x, y - 1);
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...";
90 * Help
92 void
93 HLP(int u __unused)
95 char c;
97 string_print(enter_string);
98 string_print(help_string);
99 flush();
100 c=getchar();
101 RD(0);
102 return;
105 void
106 ST(int u __unused)
108 raw_mode(OFF);
109 kill(getpid(), SIGTSTP);
110 raw_mode(ON);
111 RD(0);
115 * Move one line down.
117 void
118 DN(int u __unused)
120 if (y == last_y) { /* Last line of screen. Scroll one line */
121 if (bot_line->next == tail && bot_line->text[0] != '\n') {
122 dummy_line(); /* Create new empty line */
123 DN(0);
124 return;
126 else {
127 forward_scroll();
128 move_to(x, y);
131 else /* Move to next line */
132 move_to(x, y + 1);
136 * Move left one position.
138 void
139 LF(int u __unused)
141 if (x == 0 && get_shift(cur_line->shift_count) == 0) {/* Begin of line */
142 if (cur_line->prev != header) {
143 UP(0); /* Move one line up */
144 move_to(LINE_END, y);
147 else
148 move_to(x - 1, y);
152 * Move right one position.
154 void
155 RT(int u __unused)
157 if (*cur_text == '\n') {
158 if (cur_line->next != tail) { /* Last char of file */
159 DN(0); /* Move one line down */
160 move_to(LINE_START, y);
163 else
164 move_to(x + 1, y);
168 * Move to coordinates [0, 0] on screen.
170 void
171 HIGH(int u __unused)
173 move_to(0, 0);
177 * Move to coordinates [0, YMAX] on screen.
179 void
180 LOW(int u __unused)
182 move_to(0, last_y);
186 * Move to begin of line.
188 void
189 BL(int u __unused)
191 move_to(LINE_START, y);
195 * Move to end of line.
197 void
198 EL(int u __unused)
200 move_to(LINE_END, y);
204 * GOTO() prompts for a linenumber and moves to that line.
206 void
207 GOTO(int u __unused)
209 int number;
210 LINE *line;
212 if (get_number("Please enter line number.", &number) == ERRORS)
213 return;
215 if (number <= 0 || (line = proceed(header->next, number - 1)) == tail)
216 error("Illegal line number: ", num_out((long) number));
217 else
218 move_to(x, find_y(line));
222 * Scroll forward one page or to eof, whatever comes first. (Bot_line becomes
223 * top_line of display.) Try to leave the cursor on the same line. If this is
224 * not possible, leave cursor on the line halfway the page.
226 void
227 PD(int u __unused)
229 int i;
231 for (i = 0; i < screenmax; i++)
232 if (forward_scroll() == ERRORS)
233 break; /* EOF reached */
234 if (y - i < 0) /* Line no longer on screen */
235 move_to(0, screenmax >> 1);
236 else
237 move_to(0, y - i);
242 * Scroll backwards one page or to top of file, whatever comes first. (Top_line
243 * becomes bot_line of display). The very bottom line (YMAX) is always blank.
244 * Try to leave the cursor on the same line. If this is not possible, leave
245 * cursor on the line halfway the page.
247 void
248 PU(int u __unused)
250 int i;
252 for (i = 0; i < screenmax; i++)
253 if (reverse_scroll() == ERRORS)
254 break; /* Top of file reached */
255 set_cursor(0, ymax); /* Erase very bottom line */
256 #ifdef UNIX
257 tputs(CE, 0, _putchar);
258 #else
259 string_print(blank_line);
260 #endif /* UNIX */
261 if (y + i > screenmax) /* line no longer on screen */
262 move_to(0, screenmax >> 1);
263 else
264 move_to(0, y + i);
268 * Go to top of file, scrolling if possible, else redrawing screen.
270 void
271 HO(int u __unused)
273 if (proceed(top_line, -screenmax) == header)
274 PU(0); /* It fits. Let PU do it */
275 else {
276 reset(header->next, 0);/* Reset top_line, etc. */
277 RD(0); /* Display full page */
279 move_to(LINE_START, 0);
283 * Go to last line of file, scrolling if possible, else redrawing screen
285 void
286 EF(int u __unused)
288 if (tail->prev->text[0] != '\n')
289 dummy_line();
290 if (proceed(bot_line, screenmax) == tail)
291 PD(0); /* It fits. Let PD do it */
292 else {
293 reset(proceed(tail->prev, -screenmax), screenmax);
294 RD(0); /* Display full page */
296 move_to(LINE_START, last_y);
300 * Scroll one line up. Leave the cursor on the same line (if possible).
302 void
303 SU(int u __unused)
305 if (top_line->prev == header) /* Top of file. Can't scroll */
306 return;
308 reverse_scroll();
309 set_cursor(0, ymax); /* Erase very bottom line */
310 #ifdef UNIX
311 tputs(CE, 0, _putchar);
312 #else
313 string_print(blank_line);
314 #endif /* UNIX */
315 move_to(x, (y == screenmax) ? screenmax : y + 1);
319 * Scroll one line down. Leave the cursor on the same line (if possible).
321 void
322 SD(int u __unused)
324 if (forward_scroll() != ERRORS)
325 move_to(x, (y == 0) ? 0 : y - 1);
326 else
327 set_cursor(x, y);
331 * Perform a forward scroll. It returns ERRORS if we're at the last line of the
332 * file.
335 forward_scroll(void)
337 if (bot_line->next == tail) /* Last line of file. No dice */
338 return ERRORS;
339 top_line = top_line->next;
340 bot_line = bot_line->next;
341 cur_line = cur_line->next;
342 set_cursor(0, ymax);
343 line_print(bot_line);
345 return FINE;
349 * Perform a backwards scroll. It returns ERRORS if we're at the first line
350 * of the file.
353 reverse_scroll(void)
355 if (top_line->prev == header)
356 return ERRORS; /* Top of file. Can't scroll */
358 if (last_y != screenmax) /* Reset last_y if necessary */
359 last_y++;
360 else
361 bot_line = bot_line->prev; /* Else adjust bot_line */
362 top_line = top_line->prev;
363 cur_line = cur_line->prev;
365 /* Perform the scroll */
366 set_cursor(0, 0);
367 #ifdef UNIX
368 tputs(AL, 0, _putchar);
369 #else
370 string_print(rev_scroll);
371 #endif /* UNIX */
372 set_cursor(0, 0);
373 line_print(top_line);
375 return FINE;
379 * A word is defined as a number of non-blank characters separated by tabs
380 * spaces or linefeeds.
384 * MP() moves to the start of the previous word. A word is defined as a
385 * number of non-blank characters separated by tabs spaces or linefeeds.
387 void
388 MP(int u __unused)
390 move_previous_word(NO_DELETE);
393 void
394 move_previous_word(FLAG remove)
396 char *begin_line;
397 char *textp;
398 char start_char = *cur_text;
399 char *start_pos = cur_text;
401 /* Fist check if we're at the beginning of line. */
402 if (cur_text == cur_line->text) {
403 if (cur_line->prev == header)
404 return;
405 start_char = '\0';
408 LF(0);
410 begin_line = cur_line->text;
411 textp = cur_text;
413 /* Check if we're in the middle of a word. */
414 if (!alpha(*textp) || !alpha(start_char)) {
415 while (textp != begin_line && (white_space(*textp) || *textp == '\n'))
416 textp--;
419 /* Now we're at the end of previous word. Skip non-blanks until a blank comes */
420 while (textp != begin_line && alpha(*textp))
421 textp--;
423 /* Go to the next char if we're not at the beginning of the line */
424 if (textp != begin_line && *textp != '\n')
425 textp++;
427 /* Find the x-coordinate of this address, and move to it */
428 move_address(textp);
429 if (remove == DELETE)
430 delete(cur_line, textp, cur_line, start_pos);
434 * MN() moves to the start of the next word. A word is defined as a number of
435 * non-blank characters separated by tabs spaces or linefeeds. Always keep in
436 * mind that the pointer shouldn't pass the '\n'.
438 void
439 MN(int u __unused)
441 move_next_word(NO_DELETE);
444 void
445 move_next_word(FLAG remove)
447 char *textp = cur_text;
449 /* Move to the end of the current word. */
450 while (*textp != '\n' && alpha(*textp))
451 textp++;
453 /* Skip all white spaces */
454 while (*textp != '\n' && white_space(*textp))
455 textp++;
456 /* If we're deleting. delete the text in between */
457 if (remove == DELETE) {
458 delete(cur_line, cur_text, cur_line, textp);
459 return;
462 /* If we're at end of line. move to the first word on the next line. */
463 if (*textp == '\n' && cur_line->next != tail) {
464 DN(0);
465 move_to(LINE_START, y);
466 textp = cur_text;
467 while (*textp != '\n' && white_space(*textp))
468 textp++;
470 move_address(textp);
473 /* ======================================================================== *
474 * Modify Commands *
475 * ======================================================================== */
478 * DCC deletes the character under the cursor. If this character is a '\n' the
479 * current line is joined with the next one.
480 * If this character is the only character of the line, the current line will
481 * be deleted.
483 void
484 DCC(int u __unused)
486 if (*cur_text == '\n')
487 delete(cur_line,cur_text, cur_line->next,cur_line->next->text);
488 else
489 delete(cur_line, cur_text, cur_line, cur_text + 1);
493 * DPC deletes the character on the left side of the cursor. If the cursor is
494 * at the beginning of the line, the last character if the previous line is
495 * deleted.
497 void
498 DPC(int u __unused)
500 if (x == 0 && cur_line->prev == header)
501 return; /* Top of file */
503 LF(0); /* Move one left */
504 DCC(0); /* Delete character under cursor */
508 * DLN deletes all characters until the end of the line. If the current
509 * character is a '\n', then delete that char.
511 void
512 DLN(int u __unused)
514 if (*cur_text == '\n')
515 DCC(0);
516 else
517 delete(cur_line, cur_text, cur_line, cur_text + length_of(cur_text) -1);
521 * DNW() deletes the next word (as described in MN())
523 void
524 DNW(int u __unused)
526 if (*cur_text == '\n')
527 DCC(0);
528 else
529 move_next_word(DELETE);
533 * DPW() deletes the next word (as described in MP())
535 void
536 DPW(int u __unused)
538 if (cur_text == cur_line->text)
539 DPC(0);
540 else
541 move_previous_word(DELETE);
545 * Insert character `character' at current location.
547 void
548 S(int character)
550 static char buffer[2];
552 buffer[0] = character;
553 /* Insert the character */
554 if (insert(cur_line, cur_text, buffer) == ERRORS)
555 return;
557 /* Fix screen */
558 if (character == '\n') {
559 set_cursor(0, y);
560 if (y == screenmax) { /* Can't use display */
561 line_print(cur_line);
562 forward_scroll();
564 else {
565 reset(top_line, y); /* Reset pointers */
566 display(0, y, cur_line, last_y - y);
568 move_to(0, (y == screenmax) ? y : y + 1);
570 else if (x + 1 == XBREAK)/* If line must be shifted, just call move_to*/
571 move_to(x + 1, y);
572 else { /* else display rest of line */
573 put_line(cur_line, x, FALSE);
574 move_to(x + 1, y);
579 * CTL inserts a control-char at the current location. A message that this
580 * function is called is displayed at the status line.
582 void
583 CTL(int u __unused)
585 char ctrl;
587 status_line("Enter control character.", NIL_PTR);
588 if ((ctrl = getchar()) >= '\01' && ctrl <= '\037') {
589 S(ctrl); /* Insert the char */
590 clear_status();
592 else
593 error ("Unknown control character", NIL_PTR);
597 * LIB insert a line at the current position and moves back to the end of
598 * the previous line.
600 void
601 LIB(int u __unused)
603 S('\n'); /* Insert the line */
604 UP(0); /* Move one line up */
605 move_to(LINE_END, y); /* Move to end of this line */
609 * Line_insert() inserts a new line with text pointed to by `string'.
610 * It returns the address of the new line.
612 LINE *
613 line_insert(LINE *line, const char *string, int len)
615 LINE *new_line;
617 /* Allocate space for LINE structure and text */
618 new_line = install_line(string, len);
620 /* Install the line into the double linked list */
621 new_line->prev = line;
622 new_line->next = line->next;
623 line->next = new_line;
624 new_line->next->prev = new_line;
626 /* Increment nlines */
627 nlines++;
629 return new_line;
633 * Insert() insert the string `string' at the given line and location.
636 insert(LINE *line, char *location, char *string)
638 char *bufp = text_buffer; /* Buffer for building line */
639 char *textp = line->text;
641 if (length_of(textp) + length_of(string) >= MAX_CHARS) {
642 error("Line too long", NIL_PTR);
643 return ERRORS;
646 modified = TRUE; /* File has been modified */
648 /* Copy part of line until `location' has been reached */
649 while (textp != location)
650 *bufp++ = *textp++;
652 /* Insert string at this location */
653 while (*string != '\0')
654 *bufp++ = *string++;
655 *bufp = '\0';
657 if (*(string - 1) == '\n') /* Insert a new line */
658 line_insert(line, location, length_of(location));
659 else /* Append last part of line */
660 copy_string(bufp, location);
662 /* Install the new text in this line */
663 free_space(line->text);
664 line->text = alloc(length_of(text_buffer) + 1);
665 copy_string(line->text, text_buffer);
667 return FINE;
671 * Line_delete() deletes the argument line out of the line list. The pointer to
672 * the next line is returned.
674 LINE *
675 line_delete(LINE *line)
677 LINE *next_line = line->next;
679 /* Delete the line */
680 line->prev->next = line->next;
681 line->next->prev = line->prev;
683 /* Free allocated space */
684 free_space(line->text);
685 free_space((char*)line);
687 /* Decrement nlines */
688 nlines--;
690 return next_line;
694 * Delete() deletes all the characters (including newlines) between the
695 * startposition and endposition and fixes the screen accordingly. It
696 * returns the number of lines deleted.
698 void
699 delete(LINE *start_line, char *start_textp,
700 LINE *end_line, char *end_textp)
702 char *textp = start_line->text;
703 char *bufp = text_buffer; /* Storage for new line->text */
704 LINE *line, *stop;
705 int line_cnt = 0; /* Nr of lines deleted */
706 int count = 0;
707 int shift = 0; /* Used in shift calculation */
708 int nx = x;
710 modified = TRUE; /* File has been modified */
712 /* Set up new line. Copy first part of start line until start_position. */
713 while (textp < start_textp) {
714 *bufp++ = *textp++;
715 count++;
718 /* Check if line doesn't exceed MAX_CHARS */
719 if (count + length_of(end_textp) >= MAX_CHARS) {
720 error("Line too long", NIL_PTR);
721 return;
724 /* Copy last part of end_line if end_line is not tail */
725 copy_string(bufp, (end_textp != NIL_PTR) ? end_textp : "\n");
727 /* Delete all lines between start and end_position (including end_line) */
728 line = start_line->next;
729 stop = end_line->next;
730 while (line != stop && line != tail) {
731 line = line_delete(line);
732 line_cnt++;
735 /* Check if last line of file should be deleted */
736 if (end_textp == NIL_PTR && length_of(start_line->text) == 1 && nlines > 1) {
737 start_line = start_line->prev;
738 line_delete(start_line->next);
739 line_cnt++;
741 else { /* Install new text */
742 free_space(start_line->text);
743 start_line->text = alloc(length_of(text_buffer) + 1);
744 copy_string(start_line->text, text_buffer);
747 /* Fix screen. First check if line is shifted. Perhaps we should shift it back*/
748 if (get_shift(start_line->shift_count)) {
749 shift = (XBREAK - count_chars(start_line)) / SHIFT_SIZE;
750 if (shift > 0) { /* Shift line `shift' back */
751 if (shift >= get_shift(start_line->shift_count))
752 start_line->shift_count = 0;
753 else
754 start_line->shift_count -= shift;
755 nx += shift * SHIFT_SIZE;/* Reset x value */
759 if (line_cnt == 0) { /* Check if only one line changed */
760 if (shift > 0) { /* Reprint whole line */
761 set_cursor(0, y);
762 line_print(start_line);
764 else { /* Just display last part of line */
765 set_cursor(x, y);
766 put_line(start_line, x, TRUE);
768 move_to(nx, y); /* Reset cur_text */
769 return;
772 shift = last_y; /* Save value */
773 reset(top_line, y);
774 display(0, y, start_line, shift - y);
775 move_to((line_cnt == 1) ? nx : 0, y);
778 /* ======================================================================== *
779 * Yank Commands *
780 * ======================================================================== */
782 LINE *mark_line; /* For marking position. */
783 char *mark_text;
784 int lines_saved; /* Nr of lines in buffer */
787 * PT() inserts the buffer at the current location.
789 void
790 PT(int u __unused)
792 int fd; /* File descriptor for buffer */
794 if ((fd = scratch_file(READ)) == ERRORS)
795 error("Buffer is empty.", NIL_PTR);
796 else {
797 file_insert(fd, FALSE);/* Insert the buffer */
798 close(fd);
803 * IF() prompt for a filename and inserts the file at the current location
804 * in the file.
806 void
807 IF(int u __unused)
809 int fd; /* File descriptor of file */
810 char name[LINE_LEN]; /* Buffer for file name */
812 /* Get the file name */
813 if (get_file("Get and insert file:", name) != FINE)
814 return;
816 if ((fd = open(name, 0)) < 0)
817 error("Cannot open ", name);
818 else {
819 file_insert(fd, TRUE); /* Insert the file */
820 close(fd);
825 * File_insert() inserts a an opened file (as given by filedescriptor fd)
826 * at the current location.
828 void
829 file_insert(int fd, FLAG old_pos)
831 char line_buffer[MAX_CHARS]; /* Buffer for next line */
832 LINE *line = cur_line;
833 int line_count = nlines; /* Nr of lines inserted */
834 LINE *page = cur_line;
835 int ret = ERRORS;
837 /* Get the first piece of text (might be ended with a '\n') from fd */
838 if (get_line(fd, line_buffer) == ERRORS)
839 return; /* Empty file */
841 /* Insert this text at the current location. */
842 if (insert(line, cur_text, line_buffer) == ERRORS)
843 return;
845 /* Repeat getting lines (and inserting lines) until EOF is reached */
846 while ((ret = get_line(fd, line_buffer)) != ERRORS && ret != NO_LINE)
847 line = line_insert(line, line_buffer, ret);
849 if (ret == NO_LINE) { /* Last line read not ended by a '\n' */
850 line = line->next;
851 insert(line, line->text, line_buffer);
854 /* Calculate nr of lines added */
855 line_count = nlines - line_count;
857 /* Fix the screen */
858 if (line_count == 0) { /* Only one line changed */
859 set_cursor(0, y);
860 line_print(line);
861 move_to((old_pos == TRUE) ? x : x + length_of(line_buffer), y);
863 else { /* Several lines changed */
864 reset(top_line, y); /* Reset pointers */
865 while (page != line && page != bot_line->next)
866 page = page->next;
867 if (page != bot_line->next || old_pos == TRUE)
868 display(0, y, cur_line, screenmax - y);
869 if (old_pos == TRUE)
870 move_to(x, y);
871 else if (ret == NO_LINE)
872 move_to(length_of(line_buffer), find_y(line));
873 else
874 move_to(0, find_y(line->next));
877 /* If nr of added line >= REPORT, print the count */
878 if (line_count >= REPORT)
879 status_line(num_out((long) line_count), " lines added.");
883 * WB() writes the buffer (yank_file) into another file, which
884 * is prompted for.
886 void
887 WB(int u __unused)
889 int new_fd; /* Filedescriptor to copy file */
890 int yank_fd; /* Filedescriptor to buffer */
891 int cnt; /* Count check for read/write */
892 int ret = 0; /* Error check for write */
893 char file[LINE_LEN]; /* Output file */
895 /* Checkout the buffer */
896 if ((yank_fd = scratch_file(READ)) == ERRORS) {
897 error("Buffer is empty.", NIL_PTR);
898 return;
901 /* Get file name */
902 if (get_file("Write buffer to file:", file) != FINE)
903 return;
905 /* Creat the new file */
906 if ((new_fd = creat(file, 0644)) < 0) {
907 error("Cannot create ", file);
908 return;
911 status_line("Writing ", file);
913 /* Copy buffer into file */
914 while ((cnt = read(yank_fd, text_buffer, sizeof(text_buffer))) > 0)
915 if (write(new_fd, text_buffer, cnt) != cnt) {
916 bad_write(new_fd);
917 ret = ERRORS;
918 break;
921 /* Clean up open files and status_line */
922 close(new_fd);
923 close(yank_fd);
925 if (ret != ERRORS) /* Bad write */
926 file_status("Wrote", chars_saved, file, lines_saved, TRUE, FALSE);
930 * MA sets mark_line (mark_text) to the current line (text pointer).
932 void
933 MA(int u __unused)
935 mark_line = cur_line;
936 mark_text = cur_text;
937 status_line("Mark set", NIL_PTR);
941 * YA() puts the text between the marked position and the current
942 * in the buffer.
944 void
945 YA(int u __unused)
947 set_up(NO_DELETE);
951 * DT() is essentially the same as YA(), but in DT() the text is deleted.
953 void
954 DT(int u __unused)
956 set_up(DELETE);
960 * Set_up is an interface to the actual yank. It calls checkmark () to check
961 * if the marked position is still valid. If it is, yank is called with the
962 * arguments in the right order.
964 * parameter
965 * remove: DELETE if text should be deleted
967 void
968 set_up(FLAG remove)
970 switch (checkmark()) {
971 case NOT_VALID :
972 error("Mark not set.", NIL_PTR);
973 return;
974 case SMALLER :
975 yank(mark_line, mark_text, cur_line, cur_text, remove);
976 break;
977 case BIGGER :
978 yank(cur_line, cur_text, mark_line, mark_text, remove);
979 break;
980 case SAME : /* Ignore stupid behaviour */
981 yank_status = EMPTY;
982 chars_saved = 0L;
983 status_line("0 characters saved in buffer.", NIL_PTR);
984 break;
989 * Check_mark() checks if mark_line and mark_text are still valid pointers. If
990 * they are it returns SMALLER if the marked position is before the current,
991 * BIGGER if it isn't or SAME if somebody didn't get the point.
992 * NOT_VALID is returned when mark_line and/or mark_text are no longer valid.
993 * Legal() checks if mark_text is valid on the mark_line.
995 FLAG
996 checkmark(void)
998 LINE *line;
999 FLAG cur_seen = FALSE;
1001 /* Special case: check is mark_line and cur_line are the same. */
1002 if (mark_line == cur_line) {
1003 if (mark_text == cur_text) /* Even same place */
1004 return SAME;
1005 if (legal() == ERRORS) /* mark_text out of range */
1006 return NOT_VALID;
1007 return (mark_text < cur_text) ? SMALLER : BIGGER;
1010 /* Start looking for mark_line in the line structure */
1011 for (line = header->next; line != tail; line = line->next) {
1012 if (line == cur_line)
1013 cur_seen = TRUE;
1014 else if (line == mark_line)
1015 break;
1018 /* If we found mark_line (line != tail) check for legality of mark_text */
1019 if (line == tail || legal() == ERRORS)
1020 return NOT_VALID;
1022 /* cur_seen is TRUE if cur_line is before mark_line */
1023 return (cur_seen == TRUE) ? BIGGER : SMALLER;
1027 * Legal() checks if mark_text is still a valid pointer.
1030 legal(void)
1032 char *textp = mark_line->text;
1034 /* Locate mark_text on mark_line */
1035 while (textp != mark_text && *textp++ != '\0')
1037 return (*textp == '\0') ? ERRORS : FINE;
1041 * Yank puts all the text between start_position and end_position into
1042 * the buffer.
1043 * The caller must check that the arguments to yank() are valid. (E.g. in
1044 * the right order)
1046 * parameter
1047 * remove: DELETE if text should be deleted
1049 void
1050 yank(LINE *start_line, char *start_textp, LINE *end_line, char *end_textp,
1051 FLAG remove)
1053 LINE *line = start_line;
1054 char *textp = start_textp;
1055 int fd;
1057 /* Creat file to hold buffer */
1058 if ((fd = scratch_file(WRITE)) == ERRORS)
1059 return;
1061 chars_saved = 0L;
1062 lines_saved = 0;
1063 status_line("Saving text.", NIL_PTR);
1065 /* Keep writing chars until the end_location is reached. */
1066 while (textp != end_textp) {
1067 if (write_char(fd, *textp) == ERRORS) {
1068 close(fd);
1069 return;
1071 if (*textp++ == '\n') { /* Move to the next line */
1072 line = line->next;
1073 textp = line->text;
1074 lines_saved++;
1076 chars_saved++;
1079 /* Flush the I/O buffer and close file */
1080 if (flush_buffer(fd) == ERRORS) {
1081 close(fd);
1082 return;
1084 close(fd);
1085 yank_status = VALID;
1088 * Check if the text should be deleted as well. If it should, the following
1089 * hack is used to save a lot of code. First move back to the start_position.
1090 * (This might be the location we're on now!) and them delete the text.
1091 * It might be a bit confusing the first time somebody uses it.
1092 * Delete() will fix the screen.
1094 if (remove == DELETE) {
1095 move_to(find_x(start_line, start_textp), find_y(start_line));
1096 delete(start_line, start_textp, end_line, end_textp);
1099 status_line(num_out(chars_saved), " characters saved in buffer.");
1103 * Scratch_file() creates a uniq file in /usr/tmp. If the file couldn't
1104 * be created other combinations of files are tried until a maximum
1105 * of MAXTRAILS times. After MAXTRAILS times, an error message is given
1106 * and ERRORS is returned.
1109 #define MAXTRAILS 26
1112 * parameter
1113 * mode: Can be READ or WRITE permission
1116 scratch_file(FLAG mode)
1118 static int trials = 0; /* Keep track of trails */
1119 char *y_ptr, *n_ptr;
1120 int fd = ERRORS; /* Filedescriptor to buffer */
1122 /* If yank_status == NOT_VALID, scratch_file is called for the first time */
1123 if (yank_status == NOT_VALID && mode == WRITE) { /* Create new file */
1124 /* Generate file name. */
1125 y_ptr = &yank_file[11];
1126 n_ptr = num_out((long) getpid());
1127 while ((*y_ptr = *n_ptr++) != '\0')
1128 y_ptr++;
1129 *y_ptr++ = 'a' + trials;
1130 *y_ptr = '\0';
1131 /* Check file existence */
1132 if (access(yank_file, 0) == 0 || (fd = creat(yank_file, 0644)) < 0) {
1133 if (trials++ >= MAXTRAILS) {
1134 error("Unable to creat scratchfile.", NIL_PTR);
1135 return ERRORS;
1137 else
1138 return scratch_file(mode);/* Have another go */
1141 else if ((mode == READ && (fd = open(yank_file, 0)) < 0) ||
1142 (mode == WRITE && (fd = creat(yank_file, 0644)) < 0)) {
1143 yank_status = NOT_VALID;
1144 return ERRORS;
1147 clear_buffer();
1148 return fd;
1151 /* ======================================================================== *
1152 * Search Routines *
1153 * ======================================================================== */
1156 * A regular expression consists of a sequence of:
1157 * 1. A normal character matching that character.
1158 * 2. A . matching any character.
1159 * 3. A ^ matching the begin of a line.
1160 * 4. A $ (as last character of the pattern) mathing the end of a line.
1161 * 5. A \<character> matching <character>.
1162 * 6. A number of characters enclosed in [] pairs matching any of these
1163 * characters. A list of characters can be indicated by a '-'. So
1164 * [a-z] matches any letter of the alphabet. If the first character
1165 * after the '[' is a '^' then the set is negated (matching none of
1166 * the characters).
1167 * A ']', '^' or '-' can be escaped by putting a '\' in front of it.
1168 * 7. If one of the expressions as described in 1-6 is followed by a
1169 * '*' than that expressions matches a sequence of 0 or more of
1170 * that expression.
1173 char typed_expression[LINE_LEN]; /* Holds previous expr. */
1176 * SF searches forward for an expression.
1178 void
1179 SF(int u __unused)
1181 search("Search forward:", FORWARD);
1185 * SF searches backwards for an expression.
1187 void
1188 SR(int u __unused)
1190 search("Search reverse:", REVERSE);
1194 * Get_expression() prompts for an expression. If just a return is typed, the
1195 * old expression is used. If the expression changed, compile() is called and
1196 * the returning REGEX structure is returned. It returns NIL_REG upon error.
1197 * The save flag indicates whether the expression should be appended at the
1198 * message pointer.
1200 REGEX *
1201 get_expression(const char *message)
1203 static REGEX program; /* Program of expression */
1204 char exp_buf[LINE_LEN]; /* Buffer for new expr. */
1206 if (get_string(message, exp_buf, FALSE) == ERRORS)
1207 return NIL_REG;
1209 if (exp_buf[0] == '\0' && typed_expression[0] == '\0') {
1210 error("No previous expression.", NIL_PTR);
1211 return NIL_REG;
1214 if (exp_buf[0] != '\0') { /* A new expr. is typed */
1215 copy_string(typed_expression, exp_buf);/* Save expr. */
1216 compile(exp_buf, &program); /* Compile new expression */
1219 if (program.status == REG_ERROR) { /* Error during compiling */
1220 error(program.result.err_mess, NIL_PTR);
1221 return NIL_REG;
1223 return &program;
1227 * GR() a replaces all matches from the current position until the end
1228 * of the file.
1230 void
1231 GR(int u __unused)
1233 change("Global replace:", VALID);
1237 * LR() replaces all matches on the current line.
1239 void
1240 LR(int u __unused)
1242 change("Line replace:", NOT_VALID);
1246 * Change() prompts for an expression and a substitution pattern and changes
1247 * all matches of the expression into the substitution. change() start looking
1248 * for expressions at the current line and continues until the end of the file
1249 * if the FLAG file is VALID.
1251 * parameter
1252 * message: Message to prompt for expression
1254 void
1255 change(const char *message, FLAG file)
1257 char mess_buf[LINE_LEN]; /* Buffer to hold message */
1258 char replacement[LINE_LEN]; /* Buffer to hold subst. pattern */
1259 REGEX *program; /* Program resulting from compilation */
1260 LINE *line = cur_line;
1261 char *textp;
1262 long lines = 0L; /* Nr of lines on which subs occurred */
1263 long subs = 0L; /* Nr of subs made */
1264 int page = y; /* Index to check if line is on screen*/
1266 /* Save message and get expression */
1267 copy_string(mess_buf, message);
1268 if ((program = get_expression(mess_buf)) == NIL_REG)
1269 return;
1271 /* Get substitution pattern */
1272 build_string(mess_buf, "%s %s by:", mess_buf, typed_expression);
1273 if (get_string(mess_buf, replacement, FALSE) == ERRORS)
1274 return;
1276 set_cursor(0, ymax);
1277 flush();
1278 /* Substitute until end of file */
1279 do {
1280 if (line_check(program, line->text, FORWARD)) {
1281 lines++;
1282 /* Repeat sub. on this line as long as we find a match*/
1283 do {
1284 subs++; /* Increment subs */
1285 if ((textp = substitute(line, program,replacement))
1286 == NIL_PTR)
1287 return; /* Line too long */
1288 } while ((program->status & BEGIN_LINE) != BEGIN_LINE &&
1289 (program->status & END_LINE) != END_LINE &&
1290 line_check(program, textp, FORWARD));
1291 /* Check to see if we can print the result */
1292 if (page <= screenmax) {
1293 set_cursor(0, page);
1294 line_print(line);
1297 if (page <= screenmax)
1298 page++;
1299 line = line->next;
1300 } while (line != tail && file == VALID && quit == FALSE);
1302 copy_string(mess_buf, (quit == TRUE) ? "(Aborted) " : "");
1303 /* Fix the status line */
1304 if (subs == 0L && quit == FALSE)
1305 error("Pattern not found.", NIL_PTR);
1306 else if (lines >= REPORT || quit == TRUE) {
1307 build_string(mess_buf, "%s %D substitutions on %D lines.", mess_buf,
1308 subs, lines);
1309 status_line(mess_buf, NIL_PTR);
1311 else if (file == NOT_VALID && subs >= REPORT)
1312 status_line(num_out(subs), " substitutions.");
1313 else
1314 clear_status();
1315 move_to (x, y);
1319 * Substitute() replaces the match on this line by the substitute pattern
1320 * as indicated by the program. Every '&' in the replacement is replaced by
1321 * the original match. A \ in the replacement escapes the next character.
1323 * parameter
1324 * replacement: Contains replacement pattern
1326 char *
1327 substitute(LINE *line, REGEX *program, char *replacement)
1329 char *textp = text_buffer;
1330 char *subp = replacement;
1331 char *linep = line->text;
1332 char *amp;
1334 modified = TRUE;
1336 /* Copy part of line until the beginning of the match */
1337 while (linep != program->start_ptr)
1338 *textp++ = *linep++;
1341 * Replace the match by the substitution pattern. Each occurrence of '&' is
1342 * replaced by the original match. A \ escapes the next character.
1344 while (*subp != '\0' && textp < &text_buffer[MAX_CHARS]) {
1345 if (*subp == '&') { /* Replace the original match */
1346 amp = program->start_ptr;
1347 while (amp < program->end_ptr && textp<&text_buffer[MAX_CHARS])
1348 *textp++ = *amp++;
1349 subp++;
1351 else {
1352 if (*subp == '\\' && *(subp + 1) != '\0')
1353 subp++;
1354 *textp++ = *subp++;
1358 /* Check for line length not exceeding MAX_CHARS */
1359 if (length_of(text_buffer) + length_of(program->end_ptr) >= MAX_CHARS) {
1360 error("Substitution result: line too big", NIL_PTR);
1361 return NIL_PTR;
1364 /* Append last part of line to the new build line */
1365 copy_string(textp, program->end_ptr);
1367 /* Free old line and install new one */
1368 free_space(line->text);
1369 line->text = alloc(length_of(text_buffer) + 1);
1370 copy_string(line->text, text_buffer);
1372 return(line->text + (textp - text_buffer));
1376 * Search() calls get_expression to fetch the expression. If this went well,
1377 * the function match() is called which returns the line with the next match.
1378 * If this line is the NIL_LINE, it means that a match could not be found.
1379 * Find_x() and find_y() display the right page on the screen, and return
1380 * the right coordinates for x and y. These coordinates are passed to move_to()
1382 void
1383 search(const char *message, FLAG method)
1385 REGEX *program;
1386 LINE *match_line;
1388 /* Get the expression */
1389 if ((program = get_expression(message)) == NIL_REG)
1390 return;
1392 set_cursor(0, ymax);
1393 flush();
1394 /* Find the match */
1395 if ((match_line = match(program, cur_text, method)) == NIL_LINE) {
1396 if (quit == TRUE)
1397 status_line("Aborted", NIL_PTR);
1398 else
1399 status_line("Pattern not found.", NIL_PTR);
1400 return;
1403 move(0, program->start_ptr, find_y(match_line));
1404 clear_status();
1408 * find_y() checks if the matched line is on the current page. If it is, it
1409 * returns the new y coordinate, else it displays the correct page with the
1410 * matched line in the middle and returns the new y value;
1413 find_y(LINE *match_line)
1415 LINE *line;
1416 int count = 0;
1418 /* Check if match_line is on the same page as currently displayed. */
1419 for (line = top_line; line != match_line && line != bot_line->next;
1420 line = line->next)
1421 count++;
1422 if (line != bot_line->next)
1423 return count;
1425 /* Display new page, with match_line in center. */
1426 if ((line = proceed(match_line, -(screenmax >> 1))) == header) {
1427 /* Can't display in the middle. Make first line of file top_line */
1428 count = 0;
1429 for (line = header->next; line != match_line; line = line->next)
1430 count++;
1431 line = header->next;
1433 else /* New page is displayed. Set cursor to middle of page */
1434 count = screenmax >> 1;
1436 /* Reset pointers and redraw the screen */
1437 reset(line, 0);
1438 RD(0);
1440 return count;
1443 /* Opcodes for characters */
1444 #define NORMAL 0x0200
1445 #define DOT 0x0400
1446 #define EOLN 0x0800
1447 #define STAR 0x1000
1448 #define BRACKET 0x2000
1449 #define NEGATE 0x0100
1450 #define DONE 0x4000
1452 /* Mask for opcodes and characters */
1453 #define LOW_BYTE 0x00FF
1454 #define HIGH_BYTE 0xFF00
1456 /* Previous is the contents of the previous address (ptr) points to */
1457 #define previous(ptr) (*((ptr) - 1))
1459 /* Buffer to store outcome of compilation */
1460 int exp_buffer[BLOCK_SIZE];
1462 /* Errors often used */
1463 static const char *too_long = "Regular expression too long";
1466 * Reg_error() is called by compile() is something went wrong. It set the
1467 * status of the structure to error, and assigns the error field of the union.
1469 #define reg_error(str) program->status = REG_ERROR, \
1470 program->result.err_mess = (str)
1472 * Finished() is called when everything went right during compilation. It
1473 * allocates space for the expression, and copies the expression buffer into
1474 * this field.
1476 void
1477 finished(REGEX *program, int *last_exp)
1479 int length = (last_exp - exp_buffer) * sizeof(int);
1481 /* Allocate space */
1482 program->result.expression = (int *) alloc(length);
1483 /* Copy expression. (expression consists of ints!) */
1484 bcopy(exp_buffer, program->result.expression, length);
1488 * Compile compiles the pattern into a more comprehensible form and returns a
1489 * REGEX structure. If something went wrong, the status field of the structure
1490 * is set to REG_ERROR and an error message is set into the err_mess field of
1491 * the union. If all went well the expression is saved and the expression
1492 * pointer is set to the saved (and compiled) expression.
1494 * parameter
1495 * pattern: Pointer to pattern
1497 void
1498 compile(char *pattern, REGEX *program)
1500 int *expression = exp_buffer;
1501 int *prev_char; /* Pointer to previous compiled atom */
1502 int *acct_field = NULL; /* Pointer to last BRACKET start */
1503 FLAG negate; /* Negate flag for BRACKET */
1504 char low_char; /* Index for chars in BRACKET */
1505 char c;
1507 /* Check for begin of line */
1508 if (*pattern == '^') {
1509 program->status = BEGIN_LINE;
1510 pattern++;
1512 else {
1513 program->status = 0;
1514 /* If the first character is a '*' we have to assign it here. */
1515 if (*pattern == '*') {
1516 *expression++ = '*' + NORMAL;
1517 pattern++;
1521 for (; ;) {
1522 switch (c = *pattern++) {
1523 case '.' :
1524 *expression++ = DOT;
1525 break;
1526 case '$' :
1528 * Only means EOLN if it is the last char of the pattern
1530 if (*pattern == '\0') {
1531 *expression++ = EOLN | DONE;
1532 program->status |= END_LINE;
1533 finished(program, expression);
1534 return;
1536 else
1537 *expression++ = NORMAL + '$';
1538 break;
1539 case '\0' :
1540 *expression++ = DONE;
1541 finished(program, expression);
1542 return;
1543 case '\\' :
1544 /* If last char, it must! mean a normal '\' */
1545 if (*pattern == '\0')
1546 *expression++ = NORMAL + '\\';
1547 else
1548 *expression++ = NORMAL + *pattern++;
1549 break;
1550 case '*' :
1552 * If the previous expression was a [] find out the
1553 * begin of the list, and adjust the opcode.
1555 prev_char = expression - 1;
1556 if (*prev_char & BRACKET)
1557 *(expression - (*acct_field & LOW_BYTE))|= STAR;
1558 else
1559 *prev_char |= STAR;
1560 break;
1561 case '[' :
1563 * First field in expression gives information about
1564 * the list.
1565 * The opcode consists of BRACKET and if necessary
1566 * NEGATE to indicate that the list should be negated
1567 * and/or STAR to indicate a number of sequence of this
1568 * list.
1569 * The lower byte contains the length of the list.
1571 acct_field = expression++;
1572 if (*pattern == '^') { /* List must be negated */
1573 pattern++;
1574 negate = TRUE;
1576 else
1577 negate = FALSE;
1578 while (*pattern != ']') {
1579 if (*pattern == '\0') {
1580 reg_error("Missing ]");
1581 return;
1583 if (*pattern == '\\')
1584 pattern++;
1585 *expression++ = *pattern++;
1586 if (*pattern == '-') {
1587 /* Make list of chars */
1588 low_char = previous(pattern);
1589 pattern++; /* Skip '-' */
1590 if (low_char++ > *pattern) {
1591 reg_error("Bad range in [a-z]");
1592 return;
1594 /* Build list */
1595 while (low_char <= *pattern)
1596 *expression++ = low_char++;
1597 pattern++;
1599 if (expression >= &exp_buffer[BLOCK_SIZE]) {
1600 reg_error(too_long);
1601 return;
1604 pattern++; /* Skip ']' */
1605 /* Assign length of list in acct field */
1606 if ((*acct_field = (expression - acct_field)) == 1) {
1607 reg_error("Empty []");
1608 return;
1610 /* Assign negate and bracket field */
1611 *acct_field |= BRACKET;
1612 if (negate == TRUE)
1613 *acct_field |= NEGATE;
1615 * Add BRACKET to opcode of last char in field because
1616 * a '*' may be following the list.
1618 previous(expression) |= BRACKET;
1619 break;
1620 default :
1621 *expression++ = c + NORMAL;
1623 if (expression == &exp_buffer[BLOCK_SIZE]) {
1624 reg_error(too_long);
1625 return;
1628 /* NOTREACHED */
1632 * Match gets as argument the program, pointer to place in current line to
1633 * start from and the method to search for (either FORWARD or REVERSE).
1634 * Match() will look through the whole file until a match is found.
1635 * NIL_LINE is returned if no match could be found.
1637 LINE *
1638 match(REGEX *program, char *string, FLAG method)
1640 LINE *line = cur_line;
1641 char old_char; /* For saving chars */
1643 /* Corrupted program */
1644 if (program->status == REG_ERROR)
1645 return NIL_LINE;
1647 /* Check part of text first */
1648 if (!(program->status & BEGIN_LINE)) {
1649 if (method == FORWARD) {
1650 if (line_check(program, string + 1, method) == MATCH)
1651 return cur_line; /* Match found */
1653 else if (!(program->status & END_LINE)) {
1654 old_char = *string; /* Save char and */
1655 *string = '\n'; /* Assign '\n' for line_check */
1656 if (line_check(program, line->text, method) == MATCH) {
1657 *string = old_char; /* Restore char */
1658 return cur_line; /* Found match */
1660 *string = old_char; /* No match, but restore char */
1664 /* No match in last (or first) part of line. Check out rest of file */
1665 do {
1666 line = (method == FORWARD) ? line->next : line->prev;
1667 if (line->text == NIL_PTR) /* Header/tail */
1668 continue;
1669 if (line_check(program, line->text, method) == MATCH)
1670 return line;
1671 } while (line != cur_line && quit == FALSE);
1673 /* No match found. */
1674 return NIL_LINE;
1678 * Line_check() checks the line (or rather string) for a match. Method
1679 * indicates FORWARD or REVERSE search. It scans through the whole string
1680 * until a match is found, or the end of the string is reached.
1683 line_check(REGEX *program, char *string, FLAG method)
1685 char *textp = string;
1687 /* Assign start_ptr field. We might find a match right away! */
1688 program->start_ptr = textp;
1690 /* If the match must be anchored, just check the string. */
1691 if (program->status & BEGIN_LINE)
1692 return check_string(program, string, NIL_INT);
1694 if (method == REVERSE) {
1695 /* First move to the end of the string */
1696 for (textp = string; *textp != '\n'; textp++)
1698 /* Start checking string until the begin of the string is met */
1699 while (textp >= string) {
1700 program->start_ptr = textp;
1701 if (check_string(program, textp--, NIL_INT))
1702 return MATCH;
1705 else {
1706 /* Move through the string until the end of is found */
1707 while (quit == FALSE && *textp != '\0') {
1708 program->start_ptr = textp;
1709 if (check_string(program, textp, NIL_INT))
1710 return MATCH;
1711 if (*textp == '\n')
1712 break;
1713 textp++;
1717 return NO_MATCH;
1721 * Check() checks of a match can be found in the given string. Whenever a STAR
1722 * is found during matching, then the begin position of the string is marked
1723 * and the maximum number of matches is performed. Then the function star()
1724 * is called which starts to finish the match from this position of the string
1725 * (and expression). Check() return MATCH for a match, NO_MATCH is the string
1726 * couldn't be matched or REG_ERROR for an illegal opcode in expression.
1729 check_string(REGEX *program, char *string, int *expression)
1731 int opcode; /* Holds opcode of next expr. atom */
1732 char c; /* Char that must be matched */
1733 char *mark = NULL; /* For marking position */
1734 int star_fl; /* A star has been born */
1736 if (expression == NIL_INT)
1737 expression = program->result.expression;
1739 /* Loop until end of string or end of expression */
1740 while (quit == FALSE && !(*expression & DONE) &&
1741 *string != '\0' && *string != '\n') {
1742 c = *expression & LOW_BYTE; /* Extract match char */
1743 opcode = *expression & HIGH_BYTE; /* Extract opcode */
1744 if ((star_fl = (opcode & STAR)) != 0) { /* Check star occurrence */
1745 opcode &= ~STAR; /* Strip opcode */
1746 mark = string; /* Mark current position */
1748 expression++; /* Increment expr. */
1749 switch (opcode) {
1750 case NORMAL :
1751 if (star_fl)
1752 while (*string++ == c) /* Skip all matches */
1754 else if (*string++ != c)
1755 return NO_MATCH;
1756 break;
1757 case DOT :
1758 string++;
1759 if (star_fl) /* Skip to eoln */
1760 while (*string != '\0' && *string++ != '\n')
1762 break;
1763 case NEGATE | BRACKET:
1764 case BRACKET :
1765 if (star_fl)
1766 while (in_list(expression, *string++, c, opcode)
1767 == MATCH)
1769 else if (in_list(expression, *string++, c, opcode) == NO_MATCH)
1770 return NO_MATCH;
1771 expression += c - 1; /* Add length of list */
1772 break;
1773 default :
1774 panic("Corrupted program in check_string()");
1776 if (star_fl)
1777 return star(program, mark, string, expression);
1779 if (*expression & DONE) {
1780 program->end_ptr = string; /* Match ends here */
1782 * We might have found a match. The last thing to do is check
1783 * whether a '$' was given at the end of the expression, or
1784 * the match was found on a null string. (E.g. [a-z]* always
1785 * matches) unless a ^ or $ was included in the pattern.
1787 if ((*expression & EOLN) && *string != '\n' && *string != '\0')
1788 return NO_MATCH;
1789 if (string == program->start_ptr && !(program->status & BEGIN_LINE)
1790 && !(*expression & EOLN))
1791 return NO_MATCH;
1792 return MATCH;
1794 return NO_MATCH;
1798 * Star() calls check_string() to find out the longest match possible.
1799 * It searches backwards until the (in check_string()) marked position
1800 * is reached, or a match is found.
1803 star(REGEX *program, char *end_position, char *string, int *expression)
1805 do {
1806 string--;
1807 if (check_string(program, string, expression))
1808 return MATCH;
1809 } while (string != end_position);
1811 return NO_MATCH;
1815 * In_list() checks if the given character is in the list of []. If it is
1816 * it returns MATCH. if it isn't it returns NO_MATCH. These returns values
1817 * are reversed when the NEGATE field in the opcode is present.
1820 in_list(int *list, char c, int list_length, int opcode)
1822 if (c == '\0' || c == '\n') /* End of string, never matches */
1823 return NO_MATCH;
1824 while (list_length-- > 1) { /* > 1, don't check acct_field */
1825 if ((*list & LOW_BYTE) == c)
1826 return (opcode & NEGATE) ? NO_MATCH : MATCH;
1827 list++;
1829 return (opcode & NEGATE) ? MATCH : NO_MATCH;
1833 * Dummy_line() adds an empty line at the end of the file. This is sometimes
1834 * useful in combination with the EF and DN command in combination with the
1835 * Yank command set.
1837 void
1838 dummy_line(void)
1840 line_insert(tail->prev, "\n", 1);
1841 tail->prev->shift_count = DUMMY;
1842 if (last_y != screenmax) {
1843 last_y++;
1844 bot_line = bot_line->next;