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