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