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