iwm: Fix S:N reporting in ifconfig(8)
[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]
1f0f7b35 36 * $DragonFly: src/bin/mined/mined2.c,v 1.6 2005/11/06 11:44:02 swildner 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 */
8ecf459d
SW
53void
54UP(int u __unused)
4f73fc56
MD
55{
56 if (y == 0) { /* Top line of screen. Scroll one line */
8ecf459d 57 reverse_scroll();
4f73fc56
MD
58 move_to(x, y);
59 }
60 else /* Move to previous line */
61 move_to(x, y - 1);
62}
63
8ecf459d 64static const char *help_string=
9f83a2b6 65" Mined (Minix Editor), DragonFly version.\n"
f5812cdf
MD
66"------------------------+-------------------------------+---------------------\n"
67" CURSOR MOTION | EDITING | MISC\n"
5ce3bad5 68" Up | ^N Delete next word | ^L Erase & redraw\n"
f5812cdf
MD
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"
5ce3bad5 73" ^E end of line | ^@ Set mark | cmd # times\n"
f5812cdf
MD
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 */
8ecf459d
SW
92void
93HLP(int u __unused)
f5812cdf 94{
f5812cdf
MD
95 string_print(enter_string);
96 string_print(help_string);
97 flush();
50a61867 98 getchar();
8ecf459d 99 RD(0);
f5812cdf
MD
100 return;
101}
102
8ecf459d
SW
103void
104ST(int u __unused)
5ce3bad5
MD
105{
106 raw_mode(OFF);
107 kill(getpid(), SIGTSTP);
108 raw_mode(ON);
8ecf459d 109 RD(0);
5ce3bad5
MD
110}
111
4f73fc56
MD
112/*
113 * Move one line down.
114 */
8ecf459d
SW
115void
116DN(int u __unused)
4f73fc56
MD
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 */
8ecf459d 121 DN(0);
4f73fc56
MD
122 return;
123 }
124 else {
8ecf459d 125 forward_scroll();
4f73fc56
MD
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 */
8ecf459d
SW
136void
137LF(int u __unused)
4f73fc56
MD
138{
139 if (x == 0 && get_shift(cur_line->shift_count) == 0) {/* Begin of line */
140 if (cur_line->prev != header) {
8ecf459d 141 UP(0); /* Move one line up */
4f73fc56
MD
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 */
8ecf459d
SW
152void
153RT(int u __unused)
4f73fc56
MD
154{
155 if (*cur_text == '\n') {
156 if (cur_line->next != tail) { /* Last char of file */
8ecf459d 157 DN(0); /* Move one line down */
4f73fc56
MD
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 */
8ecf459d
SW
168void
169HIGH(int u __unused)
4f73fc56
MD
170{
171 move_to(0, 0);
172}
173
174/*
175 * Move to coordinates [0, YMAX] on screen.
176 */
8ecf459d
SW
177void
178LOW(int u __unused)
4f73fc56
MD
179{
180 move_to(0, last_y);
181}
182
183/*
184 * Move to begin of line.
185 */
8ecf459d
SW
186void
187BL(int u __unused)
4f73fc56
MD
188{
189 move_to(LINE_START, y);
190}
191
192/*
193 * Move to end of line.
194 */
8ecf459d
SW
195void
196EL(int u __unused)
4f73fc56
MD
197{
198 move_to(LINE_END, y);
199}
200
201/*
202 * GOTO() prompts for a linenumber and moves to that line.
203 */
8ecf459d
SW
204void
205GOTO(int u __unused)
4f73fc56
MD
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 */
8ecf459d
SW
224void
225PD(int u __unused)
4f73fc56 226{
8ecf459d 227 int i;
4f73fc56
MD
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 */
8ecf459d
SW
245void
246PU(int u __unused)
4f73fc56 247{
8ecf459d 248 int i;
4f73fc56
MD
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 */
8ecf459d
SW
268void
269HO(int u __unused)
4f73fc56
MD
270{
271 if (proceed(top_line, -screenmax) == header)
8ecf459d 272 PU(0); /* It fits. Let PU do it */
4f73fc56
MD
273 else {
274 reset(header->next, 0);/* Reset top_line, etc. */
8ecf459d 275 RD(0); /* Display full page */
4f73fc56
MD
276 }
277 move_to(LINE_START, 0);
278}
279
280/*
281 * Go to last line of file, scrolling if possible, else redrawing screen
282 */
8ecf459d
SW
283void
284EF(int u __unused)
4f73fc56
MD
285{
286 if (tail->prev->text[0] != '\n')
287 dummy_line();
288 if (proceed(bot_line, screenmax) == tail)
8ecf459d 289 PD(0); /* It fits. Let PD do it */
4f73fc56
MD
290 else {
291 reset(proceed(tail->prev, -screenmax), screenmax);
8ecf459d 292 RD(0); /* Display full page */
4f73fc56
MD
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 */
8ecf459d
SW
300void
301SU(int u __unused)
4f73fc56
MD
302{
303 if (top_line->prev == header) /* Top of file. Can't scroll */
304 return;
305
8ecf459d 306 reverse_scroll();
4f73fc56
MD
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 */
8ecf459d
SW
319void
320SD(int u __unused)
4f73fc56
MD
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 */
8ecf459d
SW
332int
333forward_scroll(void)
4f73fc56
MD
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 */
8ecf459d
SW
350int
351reverse_scroll(void)
4f73fc56
MD
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 */
8ecf459d
SW
385void
386MP(int u __unused)
4f73fc56
MD
387{
388 move_previous_word(NO_DELETE);
389}
390
8ecf459d
SW
391void
392move_previous_word(FLAG remove)
4f73fc56 393{
8ecf459d
SW
394 char *begin_line;
395 char *textp;
4f73fc56
MD
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
8ecf459d 406 LF(0);
4f73fc56
MD
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 */
8ecf459d
SW
436void
437MN(int u __unused)
4f73fc56
MD
438{
439 move_next_word(NO_DELETE);
440}
441
8ecf459d
SW
442void
443move_next_word(FLAG remove)
4f73fc56 444{
8ecf459d 445 char *textp = cur_text;
4f73fc56
MD
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) {
8ecf459d 462 DN(0);
4f73fc56
MD
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 */
8ecf459d
SW
481void
482DCC(int u __unused)
4f73fc56
MD
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 */
8ecf459d
SW
495void
496DPC(int u __unused)
4f73fc56
MD
497{
498 if (x == 0 && cur_line->prev == header)
499 return; /* Top of file */
500
8ecf459d
SW
501 LF(0); /* Move one left */
502 DCC(0); /* Delete character under cursor */
4f73fc56
MD
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 */
8ecf459d
SW
509void
510DLN(int u __unused)
4f73fc56
MD
511{
512 if (*cur_text == '\n')
8ecf459d 513 DCC(0);
4f73fc56
MD
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 */
8ecf459d
SW
521void
522DNW(int u __unused)
4f73fc56
MD
523{
524 if (*cur_text == '\n')
8ecf459d 525 DCC(0);
4f73fc56
MD
526 else
527 move_next_word(DELETE);
528}
529
530/*
531 * DPW() deletes the next word (as described in MP())
532 */
8ecf459d
SW
533void
534DPW(int u __unused)
4f73fc56
MD
535{
536 if (cur_text == cur_line->text)
8ecf459d 537 DPC(0);
4f73fc56
MD
538 else
539 move_previous_word(DELETE);
540}
541
542/*
543 * Insert character `character' at current location.
544 */
8ecf459d
SW
545void
546S(int character)
4f73fc56
MD
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);
8ecf459d 560 forward_scroll();
4f73fc56
MD
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 */
8ecf459d
SW
580void
581CTL(int u __unused)
4f73fc56 582{
8ecf459d 583 char ctrl;
4f73fc56
MD
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 */
8ecf459d
SW
598void
599LIB(int u __unused)
4f73fc56
MD
600{
601 S('\n'); /* Insert the line */
8ecf459d 602 UP(0); /* Move one line up */
4f73fc56
MD
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 */
8ecf459d
SW
610LINE *
611line_insert(LINE *line, const char *string, int len)
4f73fc56 612{
8ecf459d 613 LINE *new_line;
4f73fc56
MD
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 */
8ecf459d
SW
633int
634insert(LINE *line, char *location, char *string)
4f73fc56 635{
8ecf459d
SW
636 char *bufp = text_buffer; /* Buffer for building line */
637 char *textp = line->text;
4f73fc56
MD
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 */
1f0f7b35 656 line_insert(line, location, length_of(location));
4f73fc56
MD
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 */
8ecf459d
SW
672LINE *
673line_delete(LINE *line)
4f73fc56 674{
8ecf459d 675 LINE *next_line = line->next;
4f73fc56
MD
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 */
8ecf459d
SW
696void
697delete(LINE *start_line, char *start_textp,
698 LINE *end_line, char *end_textp)
4f73fc56 699{
8ecf459d
SW
700 char *textp = start_line->text;
701 char *bufp = text_buffer; /* Storage for new line->text */
4f73fc56
MD
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;
8ecf459d 736 line_delete(start_line->next);
4f73fc56
MD
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
780LINE *mark_line; /* For marking position. */
781char *mark_text;
782int lines_saved; /* Nr of lines in buffer */
783
784/*
785 * PT() inserts the buffer at the current location.
786 */
8ecf459d
SW
787void
788PT(int u __unused)
4f73fc56 789{
8ecf459d 790 int fd; /* File descriptor for buffer */
4f73fc56
MD
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 */
1f0f7b35 796 close(fd);
4f73fc56
MD
797 }
798}
799
800/*
801 * IF() prompt for a filename and inserts the file at the current location
802 * in the file.
803 */
8ecf459d
SW
804void
805IF(int u __unused)
4f73fc56 806{
8ecf459d 807 int fd; /* File descriptor of file */
4f73fc56
MD
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 */
1f0f7b35 818 close(fd);
4f73fc56
MD
819 }
820}
821
822/*
823 * File_insert() inserts a an opened file (as given by filedescriptor fd)
824 * at the current location.
825 */
8ecf459d
SW
826void
827file_insert(int fd, FLAG old_pos)
4f73fc56
MD
828{
829 char line_buffer[MAX_CHARS]; /* Buffer for next line */
8ecf459d
SW
830 LINE *line = cur_line;
831 int line_count = nlines; /* Nr of lines inserted */
4f73fc56
MD
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;
1f0f7b35 849 insert(line, line->text, line_buffer);
4f73fc56
MD
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 */
8ecf459d
SW
884void
885WB(int u __unused)
4f73fc56 886{
8ecf459d 887 int new_fd; /* Filedescriptor to copy file */
4f73fc56 888 int yank_fd; /* Filedescriptor to buffer */
8ecf459d 889 int cnt; /* Count check for read/write */
4f73fc56
MD
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 */
8ecf459d
SW
920 close(new_fd);
921 close(yank_fd);
4f73fc56
MD
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 */
8ecf459d
SW
930void
931MA(int u __unused)
4f73fc56
MD
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 */
8ecf459d
SW
942void
943YA(int u __unused)
4f73fc56
MD
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 */
8ecf459d
SW
951void
952DT(int u __unused)
4f73fc56
MD
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.
8ecf459d
SW
961 *
962 * parameter
963 * remove: DELETE if text should be deleted
4f73fc56 964 */
8ecf459d
SW
965void
966set_up(FLAG remove)
4f73fc56
MD
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 */
8ecf459d
SW
993FLAG
994checkmark(void)
4f73fc56 995{
8ecf459d 996 LINE *line;
4f73fc56
MD
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 */
8ecf459d
SW
1027int
1028legal(void)
4f73fc56 1029{
8ecf459d 1030 char *textp = mark_line->text;
4f73fc56
MD
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)
8ecf459d
SW
1043 *
1044 * parameter
1045 * remove: DELETE if text should be deleted
4f73fc56 1046 */
8ecf459d
SW
1047void
1048yank(LINE *start_line, char *start_textp, LINE *end_line, char *end_textp,
1049 FLAG remove)
4f73fc56 1050{
8ecf459d
SW
1051 LINE *line = start_line;
1052 char *textp = start_textp;
4f73fc56
MD
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) {
8ecf459d 1066 close(fd);
4f73fc56
MD
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) {
8ecf459d 1079 close(fd);
4f73fc56
MD
1080 return;
1081 }
8ecf459d 1082 close(fd);
4f73fc56
MD
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
8ecf459d
SW
1109/*
1110 * parameter
1111 * mode: Can be READ or WRITE permission
1112 */
1113int
1114scratch_file(FLAG mode)
4f73fc56
MD
1115{
1116 static int trials = 0; /* Keep track of trails */
8ecf459d
SW
1117 char *y_ptr, *n_ptr;
1118 int fd = ERRORS; /* Filedescriptor to buffer */
4f73fc56
MD
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
1171char typed_expression[LINE_LEN]; /* Holds previous expr. */
1172
1173/*
1174 * SF searches forward for an expression.
1175 */
8ecf459d
SW
1176void
1177SF(int u __unused)
4f73fc56
MD
1178{
1179 search("Search forward:", FORWARD);
1180}
1181
1182/*
1183 * SF searches backwards for an expression.
1184 */
8ecf459d
SW
1185void
1186SR(int u __unused)
4f73fc56
MD
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 */
8ecf459d
SW
1198REGEX *
1199get_expression(const char *message)
4f73fc56
MD
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 */
8ecf459d
SW
1228void
1229GR(int u __unused)
4f73fc56
MD
1230{
1231 change("Global replace:", VALID);
1232}
1233
1234/*
1235 * LR() replaces all matches on the current line.
1236 */
8ecf459d
SW
1237void
1238LR(int u __unused)
4f73fc56
MD
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.
8ecf459d
SW
1248 *
1249 * parameter
1250 * message: Message to prompt for expression
4f73fc56 1251 */
8ecf459d
SW
1252void
1253change(const char *message, FLAG file)
4f73fc56
MD
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 */
8ecf459d
SW
1258 LINE *line = cur_line;
1259 char *textp;
4f73fc56
MD
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.
8ecf459d
SW
1320 *
1321 * parameter
1322 * replacement: Contains replacement pattern
4f73fc56 1323 */
8ecf459d
SW
1324char *
1325substitute(LINE *line, REGEX *program, char *replacement)
4f73fc56 1326{
8ecf459d
SW
1327 char *textp = text_buffer;
1328 char *subp = replacement;
4f73fc56
MD
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 */
8ecf459d
SW
1380void
1381search(const char *message, FLAG method)
4f73fc56 1382{
8ecf459d
SW
1383 REGEX *program;
1384 LINE *match_line;
4f73fc56
MD
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 */
8ecf459d
SW
1410int
1411find_y(LINE *match_line)
4f73fc56 1412{
8ecf459d
SW
1413 LINE *line;
1414 int count = 0;
4f73fc56
MD
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);
8ecf459d 1436 RD(0);
4f73fc56
MD
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 */
1458int exp_buffer[BLOCK_SIZE];
1459
1460/* Errors often used */
8ecf459d 1461static const char *too_long = "Regular expression too long";
4f73fc56
MD
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 */
8ecf459d
SW
1474void
1475finished(REGEX *program, int *last_exp)
4f73fc56 1476{
8ecf459d 1477 int length = (last_exp - exp_buffer) * sizeof(int);
4f73fc56
MD
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.
8ecf459d
SW
1491 *
1492 * parameter
1493 * pattern: Pointer to pattern
4f73fc56 1494 */
8ecf459d
SW
1495void
1496compile(char *pattern, REGEX *program)
4f73fc56 1497{
8ecf459d 1498 int *expression = exp_buffer;
4f73fc56 1499 int *prev_char; /* Pointer to previous compiled atom */
8ecf459d 1500 int *acct_field = NULL; /* Pointer to last BRACKET start */
4f73fc56
MD
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 */
8ecf459d
SW
1635LINE *
1636match(REGEX *program, char *string, FLAG method)
4f73fc56 1637{
8ecf459d 1638 LINE *line = cur_line;
4f73fc56
MD
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 */
8ecf459d
SW
1680int
1681line_check(REGEX *program, char *string, FLAG method)
4f73fc56 1682{
8ecf459d 1683 char *textp = string;
4f73fc56
MD
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 */
8ecf459d
SW
1726int
1727check_string(REGEX *program, char *string, int *expression)
4f73fc56 1728{
8ecf459d 1729 int opcode; /* Holds opcode of next expr. atom */
4f73fc56 1730 char c; /* Char that must be matched */
8ecf459d 1731 char *mark = NULL; /* For marking position */
4f73fc56
MD
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 */
8ecf459d 1742 if ((star_fl = (opcode & STAR)) != 0) { /* Check star occurrence */
4f73fc56
MD
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 */
8ecf459d
SW
1800int
1801star(REGEX *program, char *end_position, char *string, int *expression)
4f73fc56
MD
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 */
8ecf459d
SW
1817int
1818in_list(int *list, char c, int list_length, int opcode)
4f73fc56
MD
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 */
8ecf459d
SW
1835void
1836dummy_line(void)
4f73fc56 1837{
8ecf459d 1838 line_insert(tail->prev, "\n", 1);
4f73fc56
MD
1839 tail->prev->shift_count = DUMMY;
1840 if (last_y != screenmax) {
1841 last_y++;
1842 bot_line = bot_line->next;
1843 }
1844}