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