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