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