tools/llvm: Do not build with symbols
[minix3.git] / minix / commands / mined / mined2.c
blob6a2f0701fc8dfa4dd3c306c588692fcdc89ef140
1 /*
2 * Part 2 of the mined editor.
3 */
5 /* ======================================================================== *
6 * Move Commands *
7 * ======================================================================== */
9 #include "mined.h"
10 #include <string.h>
13 * Move one line up.
15 void UP(void)
17 if (y == 0) { /* Top line of screen. Scroll one line */
18 (void) reverse_scroll();
19 move_to(x, y);
21 else /* Move to previous line */
22 move_to(x, y - 1);
26 * Move one line down.
28 void DN(void)
30 if (y == last_y) { /* Last line of screen. Scroll one line */
31 if (bot_line->next == tail && bot_line->text[0] != '\n') {
32 dummy_line(); /* Create new empty line */
33 DN();
34 return;
36 else {
37 (void) forward_scroll();
38 move_to(x, y);
41 else /* Move to next line */
42 move_to(x, y + 1);
46 * Move left one position.
48 void LF(void)
50 if (x == 0 && get_shift(cur_line->shift_count) == 0) {/* Begin of line */
51 if (cur_line->prev != header) {
52 UP(); /* Move one line up */
53 move_to(LINE_END, y);
56 else
57 move_to(x - 1, y);
61 * Move right one position.
63 void RT(void)
65 if (*cur_text == '\n') {
66 if (cur_line->next != tail) { /* Last char of file */
67 DN(); /* Move one line down */
68 move_to(LINE_START, y);
71 else
72 move_to(x + 1, y);
76 * Move to coordinates [0, 0] on screen.
78 void HIGH(void)
80 move_to(0, 0);
84 * Move to coordinates [0, YMAX] on screen.
86 void LOW(void)
88 move_to(0, last_y);
92 * Move to begin of line.
94 void BL(void)
96 move_to(LINE_START, y);
100 * Move to end of line.
102 void EL(void)
104 move_to(LINE_END, y);
108 * GOTO() prompts for a linenumber and moves to that line.
110 void GOTO(void)
112 int number;
113 LINE *line;
115 if (get_number("Please enter line number.", &number) == ERRORS)
116 return;
118 if (number <= 0 || (line = proceed(header->next, number - 1)) == tail)
119 error("Illegal line number: ", num_out((long) number));
120 else
121 move_to(x, find_y(line));
125 * Scroll forward one page or to eof, whatever comes first. (Bot_line becomes
126 * top_line of display.) Try to leave the cursor on the same line. If this is
127 * not possible, leave cursor on the line halfway the page.
129 void PD(void)
131 register int i;
133 for (i = 0; i < screenmax; i++)
134 if (forward_scroll() == ERRORS)
135 break; /* EOF reached */
136 if (y - i < 0) /* Line no longer on screen */
137 move_to(0, screenmax >> 1);
138 else
139 move_to(0, y - i);
144 * Scroll backwards one page or to top of file, whatever comes first. (Top_line
145 * becomes bot_line of display). The very bottom line (YMAX) is always blank.
146 * Try to leave the cursor on the same line. If this is not possible, leave
147 * cursor on the line halfway the page.
149 void PU(void)
151 register int i;
153 for (i = 0; i < screenmax; i++)
154 if (reverse_scroll() == ERRORS)
155 break; /* Top of file reached */
156 set_cursor(0, ymax); /* Erase very bottom line */
157 #ifdef UNIX
158 tputs(CE, 0, _putchar);
159 #else
160 string_print(blank_line);
161 #endif /* UNIX */
162 if (y + i > screenmax) /* line no longer on screen */
163 move_to(0, screenmax >> 1);
164 else
165 move_to(0, y + i);
169 * Go to top of file, scrolling if possible, else redrawing screen.
171 void HO(void)
173 if (proceed(top_line, -screenmax) == header)
174 PU(); /* It fits. Let PU do it */
175 else {
176 reset(header->next, 0);/* Reset top_line, etc. */
177 RD(); /* Display full page */
179 move_to(LINE_START, 0);
183 * Go to last line of file, scrolling if possible, else redrawing screen
185 void EF(void)
187 if (tail->prev->text[0] != '\n')
188 dummy_line();
189 if (proceed(bot_line, screenmax) == tail)
190 PD(); /* It fits. Let PD do it */
191 else {
192 reset(proceed(tail->prev, -screenmax), screenmax);
193 RD(); /* Display full page */
195 move_to(LINE_START, last_y);
199 * Scroll one line up. Leave the cursor on the same line (if possible).
201 void SU(void)
203 if (top_line->prev == header) /* Top of file. Can't scroll */
204 return;
206 (void) reverse_scroll();
207 set_cursor(0, ymax); /* Erase very bottom line */
208 #ifdef UNIX
209 tputs(CE, 0, _putchar);
210 #else
211 string_print(blank_line);
212 #endif /* UNIX */
213 move_to(x, (y == screenmax) ? screenmax : y + 1);
217 * Scroll one line down. Leave the cursor on the same line (if possible).
219 void SD(void)
221 if (forward_scroll() != ERRORS)
222 move_to(x, (y == 0) ? 0 : y - 1);
223 else
224 set_cursor(x, y);
228 * Perform a forward scroll. It returns ERRORS if we're at the last line of the
229 * file.
231 int forward_scroll(void)
233 if (bot_line->next == tail) /* Last line of file. No dice */
234 return ERRORS;
235 top_line = top_line->next;
236 bot_line = bot_line->next;
237 cur_line = cur_line->next;
238 set_cursor(0, ymax);
239 line_print(bot_line);
241 return FINE;
245 * Perform a backwards scroll. It returns ERRORS if we're at the first line
246 * of the file.
248 int reverse_scroll(void)
250 if (top_line->prev == header)
251 return ERRORS; /* Top of file. Can't scroll */
253 if (last_y != screenmax) /* Reset last_y if necessary */
254 last_y++;
255 else
256 bot_line = bot_line->prev; /* Else adjust bot_line */
257 top_line = top_line->prev;
258 cur_line = cur_line->prev;
260 /* Perform the scroll */
261 set_cursor(0, 0);
262 #ifdef UNIX
263 tputs(AL, 0, _putchar);
264 #else
265 string_print(rev_scroll);
266 #endif /* UNIX */
267 set_cursor(0, 0);
268 line_print(top_line);
270 return FINE;
274 * A word is defined as a number of non-blank characters separated by tabs
275 * spaces or linefeeds.
279 * MP() moves to the start of the previous word. A word is defined as a
280 * number of non-blank characters separated by tabs spaces or linefeeds.
282 void MP(void)
284 move_previous_word(NO_DELETE);
287 void move_previous_word(FLAG remove)
289 register char *begin_line;
290 register char *textp;
291 char start_char = *cur_text;
292 char *start_pos = cur_text;
294 /* Fist check if we're at the beginning of line. */
295 if (cur_text == cur_line->text) {
296 if (cur_line->prev == header)
297 return;
298 start_char = '\0';
301 LF();
303 begin_line = cur_line->text;
304 textp = cur_text;
306 /* Check if we're in the middle of a word. */
307 if (!alpha(*textp) || !alpha(start_char)) {
308 while (textp != begin_line && (white_space(*textp) || *textp == '\n'))
309 textp--;
312 /* Now we're at the end of previous word. Skip non-blanks until a blank comes */
313 while (textp != begin_line && alpha(*textp))
314 textp--;
316 /* Go to the next char if we're not at the beginning of the line */
317 if (textp != begin_line && *textp != '\n')
318 textp++;
320 /* Find the x-coordinate of this address, and move to it */
321 move_address(textp);
322 if (remove == DELETE)
323 delete(cur_line, textp, cur_line, start_pos);
327 * MN() moves to the start of the next word. A word is defined as a number of
328 * non-blank characters separated by tabs spaces or linefeeds. Always keep in
329 * mind that the pointer shouldn't pass the '\n'.
331 void MN(void)
333 move_next_word(NO_DELETE);
336 void move_next_word(FLAG remove)
338 register char *textp = cur_text;
340 /* Move to the end of the current word. */
341 while (*textp != '\n' && alpha(*textp))
342 textp++;
344 /* Skip all white spaces */
345 while (*textp != '\n' && white_space(*textp))
346 textp++;
347 /* If we're deleting. delete the text in between */
348 if (remove == DELETE) {
349 delete(cur_line, cur_text, cur_line, textp);
350 return;
353 /* If we're at end of line. move to the first word on the next line. */
354 if (*textp == '\n' && cur_line->next != tail) {
355 DN();
356 move_to(LINE_START, y);
357 textp = cur_text;
358 while (*textp != '\n' && white_space(*textp))
359 textp++;
361 move_address(textp);
364 /* ======================================================================== *
365 * Modify Commands *
366 * ======================================================================== */
369 * DCC deletes the character under the cursor. If this character is a '\n' the
370 * current line is joined with the next one.
371 * If this character is the only character of the line, the current line will
372 * be deleted.
374 void DCC(void)
376 if (*cur_text == '\n')
377 delete(cur_line,cur_text, cur_line->next,cur_line->next->text);
378 else
379 delete(cur_line, cur_text, cur_line, cur_text + 1);
383 * DPC deletes the character on the left side of the cursor. If the cursor is
384 * at the beginning of the line, the last character if the previous line is
385 * deleted.
387 void DPC(void)
389 if (x == 0 && cur_line->prev == header)
390 return; /* Top of file */
392 LF(); /* Move one left */
393 DCC(); /* Delete character under cursor */
397 * DLN deletes all characters until the end of the line. If the current
398 * character is a '\n', then delete that char.
400 void DLN(void)
402 if (*cur_text == '\n')
403 DCC();
404 else
405 delete(cur_line, cur_text, cur_line, cur_text + length_of(cur_text) -1);
409 * DNW() deletes the next word (as described in MN())
411 void DNW(void)
413 if (*cur_text == '\n')
414 DCC();
415 else
416 move_next_word(DELETE);
420 * DPW() deletes the next word (as described in MP())
422 void DPW(void)
424 if (cur_text == cur_line->text)
425 DPC();
426 else
427 move_previous_word(DELETE);
431 * Insert character `character' at current location.
433 void S(int character)
435 static char buffer[2];
437 buffer[0] = character;
438 /* Insert the character */
439 if (insert(cur_line, cur_text, buffer) == ERRORS)
440 return;
442 /* Fix screen */
443 if (character == '\n') {
444 set_cursor(0, y);
445 if (y == screenmax) { /* Can't use display */
446 line_print(cur_line);
447 (void) forward_scroll();
449 else {
450 reset(top_line, y); /* Reset pointers */
451 display(0, y, cur_line, last_y - y);
453 move_to(0, (y == screenmax) ? y : y + 1);
455 else if (x + 1 == XBREAK)/* If line must be shifted, just call move_to*/
456 move_to(x + 1, y);
457 else { /* else display rest of line */
458 put_line(cur_line, x, FALSE);
459 move_to(x + 1, y);
464 * CTL inserts a control-char at the current location. A message that this
465 * function is called is displayed at the status line.
467 void CTL(void)
469 register char ctrl;
471 status_line("Enter control character.", NULL);
472 if ((ctrl = getchar()) >= '\01' && ctrl <= '\037') {
473 S(ctrl); /* Insert the char */
474 clear_status();
476 else
477 error ("Unknown control character", NULL);
481 * LIB insert a line at the current position and moves back to the end of
482 * the previous line.
484 void LIB(void)
486 S('\n'); /* Insert the line */
487 UP(); /* Move one line up */
488 move_to(LINE_END, y); /* Move to end of this line */
492 * Line_insert() inserts a new line with text pointed to by `string'.
493 * It returns the address of the new line.
495 LINE *line_insert(register LINE *line, char *string, int len)
497 register LINE *new_line;
499 /* Allocate space for LINE structure and text */
500 new_line = install_line(string, len);
502 /* Install the line into the double linked list */
503 new_line->prev = line;
504 new_line->next = line->next;
505 line->next = new_line;
506 new_line->next->prev = new_line;
508 /* Increment nlines */
509 nlines++;
511 return new_line;
515 * Insert() insert the string `string' at the given line and location.
517 int insert(register LINE *line, char *location, char *string)
519 register char *bufp = text_buffer; /* Buffer for building line */
520 register char *textp = line->text;
522 if (length_of(textp) + length_of(string) >= MAX_CHARS) {
523 error("Line too long", NULL);
524 return ERRORS;
527 modified = TRUE; /* File has been modified */
529 /* Copy part of line until `location' has been reached */
530 while (textp != location)
531 *bufp++ = *textp++;
533 /* Insert string at this location */
534 while (*string != '\0')
535 *bufp++ = *string++;
536 *bufp = '\0';
538 if (*(string - 1) == '\n') /* Insert a new line */
539 (void) line_insert(line, location, length_of(location));
540 else /* Append last part of line */
541 copy_string(bufp, location);
543 /* Install the new text in this line */
544 free_space(line->text);
545 line->text = alloc(length_of(text_buffer) + 1);
546 copy_string(line->text, text_buffer);
548 return FINE;
552 * Line_delete() deletes the argument line out of the line list. The pointer to
553 * the next line is returned.
555 LINE *line_delete(register LINE *line)
557 register LINE *next_line = line->next;
559 /* Delete the line */
560 line->prev->next = line->next;
561 line->next->prev = line->prev;
563 /* Free allocated space */
564 free_space(line->text);
565 free_space((char*)line);
567 /* Decrement nlines */
568 nlines--;
570 return next_line;
574 * Delete() deletes all the characters (including newlines) between the
575 * startposition and endposition and fixes the screen accordingly. It
576 * returns the number of lines deleted.
578 void delete(register LINE *start_line, char *start_textp, LINE *end_line,
579 char *end_textp)
581 register char *textp = start_line->text;
582 register char *bufp = text_buffer; /* Storage for new line->text */
583 LINE *line, *stop;
584 int line_cnt = 0; /* Nr of lines deleted */
585 int count = 0;
586 int shift = 0; /* Used in shift calculation */
587 int nx = x;
589 modified = TRUE; /* File has been modified */
591 /* Set up new line. Copy first part of start line until start_position. */
592 while (textp < start_textp) {
593 *bufp++ = *textp++;
594 count++;
597 /* Check if line doesn't exceed MAX_CHARS */
598 if (count + length_of(end_textp) >= MAX_CHARS) {
599 error("Line too long", NULL);
600 return;
603 /* Copy last part of end_line if end_line is not tail */
604 copy_string(bufp, (end_textp != NULL) ? end_textp : "\n");
606 /* Delete all lines between start and end_position (including end_line) */
607 line = start_line->next;
608 stop = end_line->next;
609 while (line != stop && line != tail) {
610 line = line_delete(line);
611 line_cnt++;
614 /* Check if last line of file should be deleted */
615 if (end_textp == NULL && length_of(start_line->text) == 1 && nlines > 1) {
616 start_line = start_line->prev;
617 (void) line_delete(start_line->next);
618 line_cnt++;
620 else { /* Install new text */
621 free_space(start_line->text);
622 start_line->text = alloc(length_of(text_buffer) + 1);
623 copy_string(start_line->text, text_buffer);
626 /* Fix screen. First check if line is shifted. Perhaps we should shift it back*/
627 if (get_shift(start_line->shift_count)) {
628 shift = (XBREAK - count_chars(start_line)) / SHIFT_SIZE;
629 if (shift > 0) { /* Shift line `shift' back */
630 if (shift >= get_shift(start_line->shift_count))
631 start_line->shift_count = 0;
632 else
633 start_line->shift_count -= shift;
634 nx += shift * SHIFT_SIZE;/* Reset x value */
638 if (line_cnt == 0) { /* Check if only one line changed */
639 if (shift > 0) { /* Reprint whole line */
640 set_cursor(0, y);
641 line_print(start_line);
643 else { /* Just display last part of line */
644 set_cursor(x, y);
645 put_line(start_line, x, TRUE);
647 move_to(nx, y); /* Reset cur_text */
648 return;
651 shift = last_y; /* Save value */
652 reset(top_line, y);
653 display(0, y, start_line, shift - y);
654 move_to((line_cnt == 1) ? nx : 0, y);
657 /* ======================================================================== *
658 * Yank Commands *
659 * ======================================================================== */
661 LINE *mark_line; /* For marking position. */
662 char *mark_text;
663 int lines_saved; /* Nr of lines in buffer */
666 * PT() inserts the buffer at the current location.
668 void PT(void)
670 register int fd; /* File descriptor for buffer */
672 if ((fd = scratch_file(READ)) == ERRORS)
673 error("Buffer is empty.", NULL);
674 else {
675 file_insert(fd, FALSE);/* Insert the buffer */
676 (void) close(fd);
681 * IF() prompt for a filename and inserts the file at the current location
682 * in the file.
684 void IF(void)
686 register int fd; /* File descriptor of file */
687 char name[LINE_LEN]; /* Buffer for file name */
689 /* Get the file name */
690 if (get_file("Get and insert file:", name) != FINE)
691 return;
693 if ((fd = open(name, 0)) < 0)
694 error("Cannot open ", name);
695 else {
696 file_insert(fd, TRUE); /* Insert the file */
697 (void) close(fd);
702 * File_insert() inserts a an opened file (as given by filedescriptor fd)
703 * at the current location.
705 void file_insert(int fd, FLAG old_pos)
707 char line_buffer[MAX_CHARS]; /* Buffer for next line */
708 register LINE *line = cur_line;
709 register int line_count = nlines; /* Nr of lines inserted */
710 LINE *page = cur_line;
711 int ret = ERRORS;
713 /* Get the first piece of text (might be ended with a '\n') from fd */
714 if (get_line(fd, line_buffer) == ERRORS)
715 return; /* Empty file */
717 /* Insert this text at the current location. */
718 if (insert(line, cur_text, line_buffer) == ERRORS)
719 return;
721 /* Repeat getting lines (and inserting lines) until EOF is reached */
722 while ((ret = get_line(fd, line_buffer)) != ERRORS && ret != NO_LINE)
723 line = line_insert(line, line_buffer, ret);
725 if (ret == NO_LINE) { /* Last line read not ended by a '\n' */
726 line = line->next;
727 (void) insert(line, line->text, line_buffer);
730 /* Calculate nr of lines added */
731 line_count = nlines - line_count;
733 /* Fix the screen */
734 if (line_count == 0) { /* Only one line changed */
735 set_cursor(0, y);
736 line_print(line);
737 move_to((old_pos == TRUE) ? x : x + length_of(line_buffer), y);
739 else { /* Several lines changed */
740 reset(top_line, y); /* Reset pointers */
741 while (page != line && page != bot_line->next)
742 page = page->next;
743 if (page != bot_line->next || old_pos == TRUE)
744 display(0, y, cur_line, screenmax - y);
745 if (old_pos == TRUE)
746 move_to(x, y);
747 else if (ret == NO_LINE)
748 move_to(length_of(line_buffer), find_y(line));
749 else
750 move_to(0, find_y(line->next));
753 /* If nr of added line >= REPORT, print the count */
754 if (line_count >= REPORT)
755 status_line(num_out((long) line_count), " lines added.");
759 * WB() writes the buffer (yank_file) into another file, which
760 * is prompted for.
762 void WB(void)
764 register int new_fd; /* Filedescriptor to copy file */
765 int yank_fd; /* Filedescriptor to buffer */
766 register int cnt; /* Count check for read/write */
767 int ret = 0; /* Error check for write */
768 char file[LINE_LEN]; /* Output file */
770 /* Checkout the buffer */
771 if ((yank_fd = scratch_file(READ)) == ERRORS) {
772 error("Buffer is empty.", NULL);
773 return;
776 /* Get file name */
777 if (get_file("Write buffer to file:", file) != FINE)
778 return;
780 /* Creat the new file */
781 if ((new_fd = creat(file, 0644)) < 0) {
782 error("Cannot create ", file);
783 return;
786 status_line("Writing ", file);
788 /* Copy buffer into file */
789 while ((cnt = read(yank_fd, text_buffer, sizeof(text_buffer))) > 0)
790 if (write(new_fd, text_buffer, cnt) != cnt) {
791 bad_write(new_fd);
792 ret = ERRORS;
793 break;
796 /* Clean up open files and status_line */
797 (void) close(new_fd);
798 (void) close(yank_fd);
800 if (ret != ERRORS) /* Bad write */
801 file_status("Wrote", chars_saved, file, lines_saved, TRUE, FALSE);
805 * MA sets mark_line (mark_text) to the current line (text pointer).
807 void MA(void)
809 mark_line = cur_line;
810 mark_text = cur_text;
811 status_line("Mark set", NULL);
815 * YA() puts the text between the marked position and the current
816 * in the buffer.
818 void YA(void)
820 set_up(NO_DELETE);
824 * DT() is essentially the same as YA(), but in DT() the text is deleted.
826 void DT(void)
828 set_up(DELETE);
832 * Set_up is an interface to the actual yank. It calls checkmark () to check
833 * if the marked position is still valid. If it is, yank is called with the
834 * arguments in the right order.
836 void set_up(FLAG remove)
838 switch (checkmark()) {
839 case NOT_VALID :
840 error("Mark not set.", NULL);
841 return;
842 case SMALLER :
843 yank(mark_line, mark_text, cur_line, cur_text, remove);
844 break;
845 case BIGGER :
846 yank(cur_line, cur_text, mark_line, mark_text, remove);
847 break;
848 case SAME : /* Ignore stupid behaviour */
849 yank_status = EMPTY;
850 chars_saved = 0L;
851 status_line("0 characters saved in buffer.", NULL);
852 break;
857 * Check_mark() checks if mark_line and mark_text are still valid pointers. If
858 * they are it returns SMALLER if the marked position is before the current,
859 * BIGGER if it isn't or SAME if somebody didn't get the point.
860 * NOT_VALID is returned when mark_line and/or mark_text are no longer valid.
861 * Legal() checks if mark_text is valid on the mark_line.
863 FLAG checkmark(void)
865 register LINE *line;
866 FLAG cur_seen = FALSE;
868 /* Special case: check is mark_line and cur_line are the same. */
869 if (mark_line == cur_line) {
870 if (mark_text == cur_text) /* Even same place */
871 return SAME;
872 if (legal() == ERRORS) /* mark_text out of range */
873 return NOT_VALID;
874 return (mark_text < cur_text) ? SMALLER : BIGGER;
877 /* Start looking for mark_line in the line structure */
878 for (line = header->next; line != tail; line = line->next) {
879 if (line == cur_line)
880 cur_seen = TRUE;
881 else if (line == mark_line)
882 break;
885 /* If we found mark_line (line != tail) check for legality of mark_text */
886 if (line == tail || legal() == ERRORS)
887 return NOT_VALID;
889 /* cur_seen is TRUE if cur_line is before mark_line */
890 return (cur_seen == TRUE) ? BIGGER : SMALLER;
894 * Legal() checks if mark_text is still a valid pointer.
896 int legal(void)
898 register char *textp = mark_line->text;
900 /* Locate mark_text on mark_line */
901 while (textp != mark_text && *textp++ != '\0')
903 return (*textp == '\0') ? ERRORS : FINE;
907 * Yank puts all the text between start_position and end_position into
908 * the buffer.
909 * The caller must check that the arguments to yank() are valid. (E.g. in
910 * the right order)
912 void yank(LINE *start_line, char *start_textp, LINE *end_line, char *end_textp,
913 FLAG remove)
915 register LINE *line = start_line;
916 register char *textp = start_textp;
917 int fd;
919 /* Creat file to hold buffer */
920 if ((fd = scratch_file(WRITE)) == ERRORS)
921 return;
923 chars_saved = 0L;
924 lines_saved = 0;
925 status_line("Saving text.", NULL);
927 /* Keep writing chars until the end_location is reached. */
928 while (textp != end_textp) {
929 if (write_char(fd, *textp) == ERRORS) {
930 (void) close(fd);
931 return;
933 if (*textp++ == '\n') { /* Move to the next line */
934 line = line->next;
935 textp = line->text;
936 lines_saved++;
938 chars_saved++;
941 /* Flush the I/O buffer and close file */
942 if (flush_buffer(fd) == ERRORS) {
943 (void) close(fd);
944 return;
946 (void) close(fd);
947 yank_status = VALID;
950 * Check if the text should be deleted as well. If it should, the following
951 * hack is used to save a lot of code. First move back to the start_position.
952 * (This might be the location we're on now!) and them delete the text.
953 * It might be a bit confusing the first time somebody uses it.
954 * Delete() will fix the screen.
956 if (remove == DELETE) {
957 move_to(find_x(start_line, start_textp), find_y(start_line));
958 delete(start_line, start_textp, end_line, end_textp);
961 status_line(num_out(chars_saved), " characters saved in buffer.");
965 * Scratch_file() creates a uniq file in /usr/tmp. If the file couldn't
966 * be created other combinations of files are tried until a maximum
967 * of MAXTRAILS times. After MAXTRAILS times, an error message is given
968 * and ERRORS is returned.
971 #define MAXTRAILS 26
973 int scratch_file(FLAG mode)
975 static int trials = 0; /* Keep track of trails */
976 register char *y_ptr, *n_ptr;
977 int fd; /* Filedescriptor to buffer */
979 /* If yank_status == NOT_VALID, scratch_file is called for the first time */
980 if (yank_status == NOT_VALID && mode == WRITE) { /* Create new file */
981 /* Generate file name. */
982 y_ptr = &yank_file[11];
983 n_ptr = num_out((long) getpid());
984 while ((*y_ptr = *n_ptr++) != '\0')
985 y_ptr++;
986 *y_ptr++ = 'a' + trials;
987 *y_ptr = '\0';
988 /* Check file existence */
989 if (access(yank_file, 0) == 0 || (fd = creat(yank_file, 0644)) < 0) {
990 if (trials++ >= MAXTRAILS) {
991 error("Unable to creat scratchfile.", NULL);
992 return ERRORS;
994 else
995 return scratch_file(mode);/* Have another go */
998 else if ((mode == READ && (fd = open(yank_file, 0)) < 0) ||
999 (mode == WRITE && (fd = creat(yank_file, 0644)) < 0)) {
1000 yank_status = NOT_VALID;
1001 return ERRORS;
1004 clear_buffer();
1005 return fd;
1008 /* ======================================================================== *
1009 * Search Routines *
1010 * ======================================================================== */
1013 * A regular expression consists of a sequence of:
1014 * 1. A normal character matching that character.
1015 * 2. A . matching any character.
1016 * 3. A ^ matching the begin of a line.
1017 * 4. A $ (as last character of the pattern) mathing the end of a line.
1018 * 5. A \<character> matching <character>.
1019 * 6. A number of characters enclosed in [] pairs matching any of these
1020 * characters. A list of characters can be indicated by a '-'. So
1021 * [a-z] matches any letter of the alphabet. If the first character
1022 * after the '[' is a '^' then the set is negated (matching none of
1023 * the characters).
1024 * A ']', '^' or '-' can be escaped by putting a '\' in front of it.
1025 * 7. If one of the expressions as described in 1-6 is followed by a
1026 * '*' than that expressions matches a sequence of 0 or more of
1027 * that expression.
1030 char typed_expression[LINE_LEN]; /* Holds previous expr. */
1033 * SF searches forward for an expression.
1035 void SF(void)
1037 search("Search forward:", FORWARD);
1041 * SF searches backwards for an expression.
1043 void SR(void)
1045 search("Search reverse:", REVERSE);
1049 * Get_expression() prompts for an expression. If just a return is typed, the
1050 * old expression is used. If the expression changed, compile() is called and
1051 * the returning REGEX structure is returned. It returns NULL upon error.
1052 * The save flag indicates whether the expression should be appended at the
1053 * message pointer.
1055 REGEX *get_expression(char *message)
1057 static REGEX program; /* Program of expression */
1058 char exp_buf[LINE_LEN]; /* Buffer for new expr. */
1060 if (get_string(message, exp_buf, FALSE) == ERRORS)
1061 return NULL;
1063 if (exp_buf[0] == '\0' && typed_expression[0] == '\0') {
1064 error("No previous expression.", NULL);
1065 return NULL;
1068 if (exp_buf[0] != '\0') { /* A new expr. is typed */
1069 copy_string(typed_expression, exp_buf);/* Save expr. */
1070 compile(exp_buf, &program); /* Compile new expression */
1073 if (program.status == REG_ERROR) { /* Error during compiling */
1074 error(program.result.err_mess, NULL);
1075 return NULL;
1077 return &program;
1081 * GR() a replaces all matches from the current position until the end
1082 * of the file.
1084 void GR(void)
1086 change("Global replace:", VALID);
1090 * LR() replaces all matches on the current line.
1092 void LR(void)
1094 change("Line replace:", NOT_VALID);
1098 * Change() prompts for an expression and a substitution pattern and changes
1099 * all matches of the expression into the substitution. change() start looking
1100 * for expressions at the current line and continues until the end of the file
1101 * if the FLAG file is VALID.
1103 void change(char *message, FLAG file)
1105 char mess_buf[LINE_LEN]; /* Buffer to hold message */
1106 char replacement[LINE_LEN]; /* Buffer to hold subst. pattern */
1107 REGEX *program; /* Program resulting from compilation */
1108 register LINE *line = cur_line;
1109 register char *textp;
1110 long lines = 0L; /* Nr of lines on which subs occurred */
1111 long subs = 0L; /* Nr of subs made */
1112 int page = y; /* Index to check if line is on screen*/
1114 /* Save message and get expression */
1115 copy_string(mess_buf, message);
1116 if ((program = get_expression(mess_buf)) == NULL)
1117 return;
1119 /* Get substitution pattern */
1120 build_string(mess_buf, "%s %s by:", mess_buf, typed_expression);
1121 if (get_string(mess_buf, replacement, FALSE) == ERRORS)
1122 return;
1124 set_cursor(0, ymax);
1125 flush();
1126 /* Substitute until end of file */
1127 do {
1128 if (line_check(program, line->text, FORWARD)) {
1129 lines++;
1130 /* Repeat sub. on this line as long as we find a match*/
1131 do {
1132 subs++; /* Increment subs */
1133 if ((textp = substitute(line, program,replacement))
1134 == NULL)
1135 return; /* Line too long */
1136 } while ((program->status & BEGIN_LINE) != BEGIN_LINE &&
1137 (program->status & END_LINE) != END_LINE &&
1138 line_check(program, textp, FORWARD));
1139 /* Check to see if we can print the result */
1140 if (page <= screenmax) {
1141 set_cursor(0, page);
1142 line_print(line);
1145 if (page <= screenmax)
1146 page++;
1147 line = line->next;
1148 } while (line != tail && file == VALID && quit == FALSE);
1150 copy_string(mess_buf, (quit == TRUE) ? "(Aborted) " : "");
1151 /* Fix the status line */
1152 if (subs == 0L && quit == FALSE)
1153 error("Pattern not found.", NULL);
1154 else if (lines >= REPORT || quit == TRUE) {
1155 build_string(mess_buf, "%s %D substitutions on %D lines.", mess_buf,
1156 subs, lines);
1157 status_line(mess_buf, NULL);
1159 else if (file == NOT_VALID && subs >= REPORT)
1160 status_line(num_out(subs), " substitutions.");
1161 else
1162 clear_status();
1163 move_to (x, y);
1167 * Substitute() replaces the match on this line by the substitute pattern
1168 * as indicated by the program. Every '&' in the replacement is replaced by
1169 * the original match. A \ in the replacement escapes the next character.
1171 char *substitute(LINE *line, REGEX *program, char *replacement)
1173 register char *textp = text_buffer;
1174 register char *subp = replacement;
1175 char *linep = line->text;
1176 char *amp;
1178 modified = TRUE;
1180 /* Copy part of line until the beginning of the match */
1181 while (linep != program->start_ptr)
1182 *textp++ = *linep++;
1185 * Replace the match by the substitution pattern. Each occurrence of '&' is
1186 * replaced by the original match. A \ escapes the next character.
1188 while (*subp != '\0' && textp < &text_buffer[MAX_CHARS]) {
1189 if (*subp == '&') { /* Replace the original match */
1190 amp = program->start_ptr;
1191 while (amp < program->end_ptr && textp<&text_buffer[MAX_CHARS])
1192 *textp++ = *amp++;
1193 subp++;
1195 else {
1196 if (*subp == '\\' && *(subp + 1) != '\0')
1197 subp++;
1198 *textp++ = *subp++;
1202 /* Check for line length not exceeding MAX_CHARS */
1203 if (length_of(text_buffer) + length_of(program->end_ptr) >= MAX_CHARS) {
1204 error("Substitution result: line too big", NULL);
1205 return NULL;
1208 /* Append last part of line to the new build line */
1209 copy_string(textp, program->end_ptr);
1211 /* Free old line and install new one */
1212 free_space(line->text);
1213 line->text = alloc(length_of(text_buffer) + 1);
1214 copy_string(line->text, text_buffer);
1216 return(line->text + (textp - text_buffer));
1220 * Search() calls get_expression to fetch the expression. If this went well,
1221 * the function match() is called which returns the line with the next match.
1222 * If this line is the NULL, it means that a match could not be found.
1223 * Find_x() and find_y() display the right page on the screen, and return
1224 * the right coordinates for x and y. These coordinates are passed to move_to()
1226 void search(char *message, FLAG method)
1228 register REGEX *program;
1229 register LINE *match_line;
1231 /* Get the expression */
1232 if ((program = get_expression(message)) == NULL)
1233 return;
1235 set_cursor(0, ymax);
1236 flush();
1237 /* Find the match */
1238 if ((match_line = match(program, cur_text, method)) == NULL) {
1239 if (quit == TRUE)
1240 status_line("Aborted", NULL);
1241 else
1242 status_line("Pattern not found.", NULL);
1243 return;
1246 move(0, program->start_ptr, find_y(match_line));
1247 clear_status();
1251 * find_y() checks if the matched line is on the current page. If it is, it
1252 * returns the new y coordinate, else it displays the correct page with the
1253 * matched line in the middle and returns the new y value;
1255 int find_y(LINE *match_line)
1257 register LINE *line;
1258 register int count = 0;
1260 /* Check if match_line is on the same page as currently displayed. */
1261 for (line = top_line; line != match_line && line != bot_line->next;
1262 line = line->next)
1263 count++;
1264 if (line != bot_line->next)
1265 return count;
1267 /* Display new page, with match_line in center. */
1268 if ((line = proceed(match_line, -(screenmax >> 1))) == header) {
1269 /* Can't display in the middle. Make first line of file top_line */
1270 count = 0;
1271 for (line = header->next; line != match_line; line = line->next)
1272 count++;
1273 line = header->next;
1275 else /* New page is displayed. Set cursor to middle of page */
1276 count = screenmax >> 1;
1278 /* Reset pointers and redraw the screen */
1279 reset(line, 0);
1280 RD();
1282 return count;
1285 /* Opcodes for characters */
1286 #define NORMAL 0x0200
1287 #define DOT 0x0400
1288 #define EOLN 0x0800
1289 #define STAR 0x1000
1290 #define BRACKET 0x2000
1291 #define NEGATE 0x0100
1292 #define DONE 0x4000
1294 /* Mask for opcodes and characters */
1295 #define LOW_BYTE 0x00FF
1296 #define HIGH_BYTE 0xFF00
1298 /* Previous is the contents of the previous address (ptr) points to */
1299 #define previous(ptr) (*((ptr) - 1))
1301 /* Buffer to store outcome of compilation */
1302 int exp_buffer[BLOCK_SIZE];
1304 /* Errors often used */
1305 char *too_long = "Regular expression too long";
1308 * Reg_error() is called by compile() is something went wrong. It set the
1309 * status of the structure to error, and assigns the error field of the union.
1311 #define reg_error(str) program->status = REG_ERROR, \
1312 program->result.err_mess = (str)
1314 * Finished() is called when everything went right during compilation. It
1315 * allocates space for the expression, and copies the expression buffer into
1316 * this field.
1318 void finished(register REGEX *program, int *last_exp)
1320 register int length = (last_exp - exp_buffer) * sizeof(int);
1322 /* Allocate space */
1323 program->result.expression = (int *) alloc(length);
1324 /* Copy expression. (expression consists of ints!) */
1325 bcopy(exp_buffer, program->result.expression, length);
1329 * Compile compiles the pattern into a more comprehensible form and returns a
1330 * REGEX structure. If something went wrong, the status field of the structure
1331 * is set to REG_ERROR and an error message is set into the err_mess field of
1332 * the union. If all went well the expression is saved and the expression
1333 * pointer is set to the saved (and compiled) expression.
1335 void compile(register char *pattern, REGEX *program)
1337 register int *expression = exp_buffer;
1338 int *prev_char; /* Pointer to previous compiled atom */
1339 int *acct_field; /* Pointer to last BRACKET start */
1340 FLAG negate; /* Negate flag for BRACKET */
1341 char low_char; /* Index for chars in BRACKET */
1342 char c;
1344 /* Check for begin of line */
1345 if (*pattern == '^') {
1346 program->status = BEGIN_LINE;
1347 pattern++;
1349 else {
1350 program->status = 0;
1351 /* If the first character is a '*' we have to assign it here. */
1352 if (*pattern == '*') {
1353 *expression++ = '*' + NORMAL;
1354 pattern++;
1358 for (; ;) {
1359 switch (c = *pattern++) {
1360 case '.' :
1361 *expression++ = DOT;
1362 break;
1363 case '$' :
1365 * Only means EOLN if it is the last char of the pattern
1367 if (*pattern == '\0') {
1368 *expression++ = EOLN | DONE;
1369 program->status |= END_LINE;
1370 finished(program, expression);
1371 return;
1373 else
1374 *expression++ = NORMAL + '$';
1375 break;
1376 case '\0' :
1377 *expression++ = DONE;
1378 finished(program, expression);
1379 return;
1380 case '\\' :
1381 /* If last char, it must! mean a normal '\' */
1382 if (*pattern == '\0')
1383 *expression++ = NORMAL + '\\';
1384 else
1385 *expression++ = NORMAL + *pattern++;
1386 break;
1387 case '*' :
1389 * If the previous expression was a [] find out the
1390 * begin of the list, and adjust the opcode.
1392 prev_char = expression - 1;
1393 if (*prev_char & BRACKET)
1394 *(expression - (*acct_field & LOW_BYTE))|= STAR;
1395 else
1396 *prev_char |= STAR;
1397 break;
1398 case '[' :
1400 * First field in expression gives information about
1401 * the list.
1402 * The opcode consists of BRACKET and if necessary
1403 * NEGATE to indicate that the list should be negated
1404 * and/or STAR to indicate a number of sequence of this
1405 * list.
1406 * The lower byte contains the length of the list.
1408 acct_field = expression++;
1409 if (*pattern == '^') { /* List must be negated */
1410 pattern++;
1411 negate = TRUE;
1413 else
1414 negate = FALSE;
1415 while (*pattern != ']') {
1416 if (*pattern == '\0') {
1417 reg_error("Missing ]");
1418 return;
1420 if (*pattern == '\\')
1421 pattern++;
1422 *expression++ = *pattern++;
1423 if (*pattern == '-') {
1424 /* Make list of chars */
1425 low_char = previous(pattern);
1426 pattern++; /* Skip '-' */
1427 if (low_char++ > *pattern) {
1428 reg_error("Bad range in [a-z]");
1429 return;
1431 /* Build list */
1432 while (low_char <= *pattern)
1433 *expression++ = low_char++;
1434 pattern++;
1436 if (expression >= &exp_buffer[BLOCK_SIZE]) {
1437 reg_error(too_long);
1438 return;
1441 pattern++; /* Skip ']' */
1442 /* Assign length of list in acct field */
1443 if ((*acct_field = (expression - acct_field)) == 1) {
1444 reg_error("Empty []");
1445 return;
1447 /* Assign negate and bracket field */
1448 *acct_field |= BRACKET;
1449 if (negate == TRUE)
1450 *acct_field |= NEGATE;
1452 * Add BRACKET to opcode of last char in field because
1453 * a '*' may be following the list.
1455 previous(expression) |= BRACKET;
1456 break;
1457 default :
1458 *expression++ = c + NORMAL;
1460 if (expression == &exp_buffer[BLOCK_SIZE]) {
1461 reg_error(too_long);
1462 return;
1465 /* NOTREACHED */
1469 * Match gets as argument the program, pointer to place in current line to
1470 * start from and the method to search for (either FORWARD or REVERSE).
1471 * Match() will look through the whole file until a match is found.
1472 * NULL is returned if no match could be found.
1474 LINE *match(REGEX *program, char *string, register FLAG method)
1476 register LINE *line = cur_line;
1477 char old_char; /* For saving chars */
1479 /* Corrupted program */
1480 if (program->status == REG_ERROR)
1481 return NULL;
1483 /* Check part of text first */
1484 if (!(program->status & BEGIN_LINE)) {
1485 if (method == FORWARD) {
1486 if (line_check(program, string + 1, method) == MATCH)
1487 return cur_line; /* Match found */
1489 else if (!(program->status & END_LINE)) {
1490 old_char = *string; /* Save char and */
1491 *string = '\n'; /* Assign '\n' for line_check */
1492 if (line_check(program, line->text, method) == MATCH) {
1493 *string = old_char; /* Restore char */
1494 return cur_line; /* Found match */
1496 *string = old_char; /* No match, but restore char */
1500 /* No match in last (or first) part of line. Check out rest of file */
1501 do {
1502 line = (method == FORWARD) ? line->next : line->prev;
1503 if (line->text == NULL) /* Header/tail */
1504 continue;
1505 if (line_check(program, line->text, method) == MATCH)
1506 return line;
1507 } while (line != cur_line && quit == FALSE);
1509 /* No match found. */
1510 return NULL;
1514 * Line_check() checks the line (or rather string) for a match. Method
1515 * indicates FORWARD or REVERSE search. It scans through the whole string
1516 * until a match is found, or the end of the string is reached.
1518 int line_check(register REGEX *program, char *string, FLAG method)
1520 register char *textp = string;
1522 /* Assign start_ptr field. We might find a match right away! */
1523 program->start_ptr = textp;
1525 /* If the match must be anchored, just check the string. */
1526 if (program->status & BEGIN_LINE)
1527 return check_string(program, string, NULL);
1529 if (method == REVERSE) {
1530 /* First move to the end of the string */
1531 for (textp = string; *textp != '\n'; textp++)
1533 /* Start checking string until the begin of the string is met */
1534 while (textp >= string) {
1535 program->start_ptr = textp;
1536 if (check_string(program, textp--, NULL))
1537 return MATCH;
1540 else {
1541 /* Move through the string until the end of is found */
1542 while (quit == FALSE && *textp != '\0') {
1543 program->start_ptr = textp;
1544 if (check_string(program, textp, NULL))
1545 return MATCH;
1546 if (*textp == '\n')
1547 break;
1548 textp++;
1552 return NO_MATCH;
1556 * Check() checks of a match can be found in the given string. Whenever a STAR
1557 * is found during matching, then the begin position of the string is marked
1558 * and the maximum number of matches is performed. Then the function star()
1559 * is called which starts to finish the match from this position of the string
1560 * (and expression). Check() return MATCH for a match, NO_MATCH is the string
1561 * couldn't be matched or REG_ERROR for an illegal opcode in expression.
1563 int check_string(REGEX *program, register char *string, int *expression)
1565 register int opcode; /* Holds opcode of next expr. atom */
1566 char c; /* Char that must be matched */
1567 char *mark; /* For marking position */
1568 int star_fl; /* A star has been born */
1570 if (expression == NULL)
1571 expression = program->result.expression;
1573 /* Loop until end of string or end of expression */
1574 while (quit == FALSE && !(*expression & DONE) &&
1575 *string != '\0' && *string != '\n') {
1576 c = *expression & LOW_BYTE; /* Extract match char */
1577 opcode = *expression & HIGH_BYTE; /* Extract opcode */
1578 if ((star_fl = (opcode & STAR))) { /* Check star occurrence */
1579 opcode &= ~STAR; /* Strip opcode */
1580 mark = string; /* Mark current position */
1582 expression++; /* Increment expr. */
1583 switch (opcode) {
1584 case NORMAL :
1585 if (star_fl)
1586 while (*string++ == c) /* Skip all matches */
1588 else if (*string++ != c)
1589 return NO_MATCH;
1590 break;
1591 case DOT :
1592 string++;
1593 if (star_fl) /* Skip to eoln */
1594 while (*string != '\0' && *string++ != '\n')
1596 break;
1597 case NEGATE | BRACKET:
1598 case BRACKET :
1599 if (star_fl)
1600 while (in_list(expression, *string++, c, opcode)
1601 == MATCH)
1603 else if (in_list(expression, *string++, c, opcode) == NO_MATCH)
1604 return NO_MATCH;
1605 expression += c - 1; /* Add length of list */
1606 break;
1607 default :
1608 panic("Corrupted program in check_string()");
1610 if (star_fl)
1611 return star(program, mark, string, expression);
1613 if (*expression & DONE) {
1614 program->end_ptr = string; /* Match ends here */
1616 * We might have found a match. The last thing to do is check
1617 * whether a '$' was given at the end of the expression, or
1618 * the match was found on a null string. (E.g. [a-z]* always
1619 * matches) unless a ^ or $ was included in the pattern.
1621 if ((*expression & EOLN) && *string != '\n' && *string != '\0')
1622 return NO_MATCH;
1623 if (string == program->start_ptr && !(program->status & BEGIN_LINE)
1624 && !(*expression & EOLN))
1625 return NO_MATCH;
1626 return MATCH;
1628 return NO_MATCH;
1632 * Star() calls check_string() to find out the longest match possible.
1633 * It searches backwards until the (in check_string()) marked position
1634 * is reached, or a match is found.
1636 int star(REGEX *program, register char *end_position, register char *string,
1637 int *expression)
1639 do {
1640 string--;
1641 if (check_string(program, string, expression))
1642 return MATCH;
1643 } while (string != end_position);
1645 return NO_MATCH;
1649 * In_list() checks if the given character is in the list of []. If it is
1650 * it returns MATCH. if it isn't it returns NO_MATCH. These returns values
1651 * are reversed when the NEGATE field in the opcode is present.
1653 int in_list(int *list, int c, register int list_length, int opcode)
1655 if (c == '\0' || c == '\n') /* End of string, never matches */
1656 return NO_MATCH;
1657 while (list_length-- > 1) { /* > 1, don't check acct_field */
1658 if ((*list & LOW_BYTE) == c)
1659 return (opcode & NEGATE) ? NO_MATCH : MATCH;
1660 list++;
1662 return (opcode & NEGATE) ? MATCH : NO_MATCH;
1666 * Dummy_line() adds an empty line at the end of the file. This is sometimes
1667 * useful in combination with the EF and DN command in combination with the
1668 * Yank command set.
1670 void dummy_line(void)
1672 (void) line_insert(tail->prev, "\n", 1);
1673 tail->prev->shift_count = DUMMY;
1674 if (last_y != screenmax) {
1675 last_y++;
1676 bot_line = bot_line->next;