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