2 * Copyright (c) 1987,1997, Prentice Hall
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:
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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.
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
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.
35 * [original code from minix codebase]
36 * $DragonFly: src/bin/mined/mined2.c,v 1.4 2005/06/30 21:12:55 corecode Exp $*
39 * Part 2 of the mined editor.
42 /* ======================================================================== *
44 * ======================================================================== */
55 if (y
== 0) { /* Top line of screen. Scroll one line */
56 (void) reverse_scroll();
59 else /* Move to previous line */
63 static char *help_string
=
64 " Mined (Minix Editor), DragonFly version.\n"
65 "------------------------+-------------------------------+---------------------\n"
66 " CURSOR MOTION | EDITING | MISC\n"
67 " Up | ^N Delete next word | ^L Erase & redraw\n"
68 " Down cursor keys | ^P Delete prev. word | screen\n"
69 " Left | ^T Delete to EOL | ^\\ Abort current\n"
70 " Right +-------------------------------+ operation\n"
71 " ^A start of line | BLOCKS | Esc repeat last\n"
72 " ^E end of line | ^@ Set mark | cmd # times\n"
73 " ^^ screen top | ^K Delete mark <--> cursor | F2 file status\n"
74 " ^_ screen bottom | ^C Save mark <--> cursor +=====================\n"
75 " ^F word fwd. | ^Y Insert the contents of | ^X EXIT\n"
76 " ^B word back | the save file at cursor | ^S run shell\n"
77 "------------------------+ ^Q Insert the contents of +=====================\n"
78 " SCREEN MOTION | the save file into new | SEARCH & REPLACE\n"
79 " Home file top | file | F3 fwd. search\n"
80 " End file bottom +-------------------------------+ SF3 bck. search\n"
81 " PgUp page up | FILES | F4 Global replace\n"
82 " PgD page down | ^G Insert a file at cursor | SF4 Line replace\n"
83 " ^D rev. scroll | ^V Visit another file +---------------------\n"
84 " ^U fwd. scroll | ^W Write current file | F1 HELP\n"
85 " ^] goto line # | |\n"
86 "------------------------+-------------------------------+---------------------\n"
87 "Press any key to continue...";
95 string_print(enter_string
);
96 string_print(help_string
);
106 kill(getpid(), SIGTSTP
);
112 * Move one line down.
116 if (y
== last_y
) { /* Last line of screen. Scroll one line */
117 if (bot_line
->next
== tail
&& bot_line
->text
[0] != '\n') {
118 dummy_line(); /* Create new empty line */
123 (void) forward_scroll();
127 else /* Move to next line */
132 * Move left one position.
136 if (x
== 0 && get_shift(cur_line
->shift_count
) == 0) {/* Begin of line */
137 if (cur_line
->prev
!= header
) {
138 UP(); /* Move one line up */
139 move_to(LINE_END
, y
);
147 * Move right one position.
151 if (*cur_text
== '\n') {
152 if (cur_line
->next
!= tail
) { /* Last char of file */
153 DN(); /* Move one line down */
154 move_to(LINE_START
, y
);
162 * Move to coordinates [0, 0] on screen.
170 * Move to coordinates [0, YMAX] on screen.
178 * Move to begin of line.
182 move_to(LINE_START
, y
);
186 * Move to end of line.
190 move_to(LINE_END
, y
);
194 * GOTO() prompts for a linenumber and moves to that line.
201 if (get_number("Please enter line number.", &number
) == ERRORS
)
204 if (number
<= 0 || (line
= proceed(header
->next
, number
- 1)) == tail
)
205 error("Illegal line number: ", num_out((long) number
));
207 move_to(x
, find_y(line
));
211 * Scroll forward one page or to eof, whatever comes first. (Bot_line becomes
212 * top_line of display.) Try to leave the cursor on the same line. If this is
213 * not possible, leave cursor on the line halfway the page.
219 for (i
= 0; i
< screenmax
; i
++)
220 if (forward_scroll() == ERRORS
)
221 break; /* EOF reached */
222 if (y
- i
< 0) /* Line no longer on screen */
223 move_to(0, screenmax
>> 1);
230 * Scroll backwards one page or to top of file, whatever comes first. (Top_line
231 * becomes bot_line of display). The very bottom line (YMAX) is always blank.
232 * Try to leave the cursor on the same line. If this is not possible, leave
233 * cursor on the line halfway the page.
239 for (i
= 0; i
< screenmax
; i
++)
240 if (reverse_scroll() == ERRORS
)
241 break; /* Top of file reached */
242 set_cursor(0, ymax
); /* Erase very bottom line */
244 tputs(CE
, 0, _putchar
);
246 string_print(blank_line
);
248 if (y
+ i
> screenmax
) /* line no longer on screen */
249 move_to(0, screenmax
>> 1);
255 * Go to top of file, scrolling if possible, else redrawing screen.
259 if (proceed(top_line
, -screenmax
) == header
)
260 PU(); /* It fits. Let PU do it */
262 reset(header
->next
, 0);/* Reset top_line, etc. */
263 RD(); /* Display full page */
265 move_to(LINE_START
, 0);
269 * Go to last line of file, scrolling if possible, else redrawing screen
273 if (tail
->prev
->text
[0] != '\n')
275 if (proceed(bot_line
, screenmax
) == tail
)
276 PD(); /* It fits. Let PD do it */
278 reset(proceed(tail
->prev
, -screenmax
), screenmax
);
279 RD(); /* Display full page */
281 move_to(LINE_START
, last_y
);
285 * Scroll one line up. Leave the cursor on the same line (if possible).
289 if (top_line
->prev
== header
) /* Top of file. Can't scroll */
292 (void) reverse_scroll();
293 set_cursor(0, ymax
); /* Erase very bottom line */
295 tputs(CE
, 0, _putchar
);
297 string_print(blank_line
);
299 move_to(x
, (y
== screenmax
) ? screenmax
: y
+ 1);
303 * Scroll one line down. Leave the cursor on the same line (if possible).
307 if (forward_scroll() != ERRORS
)
308 move_to(x
, (y
== 0) ? 0 : y
- 1);
314 * Perform a forward scroll. It returns ERRORS if we're at the last line of the
319 if (bot_line
->next
== tail
) /* Last line of file. No dice */
321 top_line
= top_line
->next
;
322 bot_line
= bot_line
->next
;
323 cur_line
= cur_line
->next
;
325 line_print(bot_line
);
331 * Perform a backwards scroll. It returns ERRORS if we're at the first line
336 if (top_line
->prev
== header
)
337 return ERRORS
; /* Top of file. Can't scroll */
339 if (last_y
!= screenmax
) /* Reset last_y if necessary */
342 bot_line
= bot_line
->prev
; /* Else adjust bot_line */
343 top_line
= top_line
->prev
;
344 cur_line
= cur_line
->prev
;
346 /* Perform the scroll */
349 tputs(AL
, 0, _putchar
);
351 string_print(rev_scroll
);
354 line_print(top_line
);
360 * A word is defined as a number of non-blank characters separated by tabs
361 * spaces or linefeeds.
365 * MP() moves to the start of the previous word. A word is defined as a
366 * number of non-blank characters separated by tabs spaces or linefeeds.
370 move_previous_word(NO_DELETE
);
373 void move_previous_word(remove
)
376 register char *begin_line
;
377 register char *textp
;
378 char start_char
= *cur_text
;
379 char *start_pos
= cur_text
;
381 /* Fist check if we're at the beginning of line. */
382 if (cur_text
== cur_line
->text
) {
383 if (cur_line
->prev
== header
)
390 begin_line
= cur_line
->text
;
393 /* Check if we're in the middle of a word. */
394 if (!alpha(*textp
) || !alpha(start_char
)) {
395 while (textp
!= begin_line
&& (white_space(*textp
) || *textp
== '\n'))
399 /* Now we're at the end of previous word. Skip non-blanks until a blank comes */
400 while (textp
!= begin_line
&& alpha(*textp
))
403 /* Go to the next char if we're not at the beginning of the line */
404 if (textp
!= begin_line
&& *textp
!= '\n')
407 /* Find the x-coordinate of this address, and move to it */
409 if (remove
== DELETE
)
410 delete(cur_line
, textp
, cur_line
, start_pos
);
414 * MN() moves to the start of the next word. A word is defined as a number of
415 * non-blank characters separated by tabs spaces or linefeeds. Always keep in
416 * mind that the pointer shouldn't pass the '\n'.
420 move_next_word(NO_DELETE
);
423 void move_next_word(remove
)
426 register char *textp
= cur_text
;
428 /* Move to the end of the current word. */
429 while (*textp
!= '\n' && alpha(*textp
))
432 /* Skip all white spaces */
433 while (*textp
!= '\n' && white_space(*textp
))
435 /* If we're deleting. delete the text in between */
436 if (remove
== DELETE
) {
437 delete(cur_line
, cur_text
, cur_line
, textp
);
441 /* If we're at end of line. move to the first word on the next line. */
442 if (*textp
== '\n' && cur_line
->next
!= tail
) {
444 move_to(LINE_START
, y
);
446 while (*textp
!= '\n' && white_space(*textp
))
452 /* ======================================================================== *
454 * ======================================================================== */
457 * DCC deletes the character under the cursor. If this character is a '\n' the
458 * current line is joined with the next one.
459 * If this character is the only character of the line, the current line will
464 if (*cur_text
== '\n')
465 delete(cur_line
,cur_text
, cur_line
->next
,cur_line
->next
->text
);
467 delete(cur_line
, cur_text
, cur_line
, cur_text
+ 1);
471 * DPC deletes the character on the left side of the cursor. If the cursor is
472 * at the beginning of the line, the last character if the previous line is
477 if (x
== 0 && cur_line
->prev
== header
)
478 return; /* Top of file */
480 LF(); /* Move one left */
481 DCC(); /* Delete character under cursor */
485 * DLN deletes all characters until the end of the line. If the current
486 * character is a '\n', then delete that char.
490 if (*cur_text
== '\n')
493 delete(cur_line
, cur_text
, cur_line
, cur_text
+ length_of(cur_text
) -1);
497 * DNW() deletes the next word (as described in MN())
501 if (*cur_text
== '\n')
504 move_next_word(DELETE
);
508 * DPW() deletes the next word (as described in MP())
512 if (cur_text
== cur_line
->text
)
515 move_previous_word(DELETE
);
519 * Insert character `character' at current location.
522 register char character
;
524 static char buffer
[2];
526 buffer
[0] = character
;
527 /* Insert the character */
528 if (insert(cur_line
, cur_text
, buffer
) == ERRORS
)
532 if (character
== '\n') {
534 if (y
== screenmax
) { /* Can't use display */
535 line_print(cur_line
);
536 (void) forward_scroll();
539 reset(top_line
, y
); /* Reset pointers */
540 display(0, y
, cur_line
, last_y
- y
);
542 move_to(0, (y
== screenmax
) ? y
: y
+ 1);
544 else if (x
+ 1 == XBREAK
)/* If line must be shifted, just call move_to*/
546 else { /* else display rest of line */
547 put_line(cur_line
, x
, FALSE
);
553 * CTL inserts a control-char at the current location. A message that this
554 * function is called is displayed at the status line.
560 status_line("Enter control character.", NIL_PTR
);
561 if ((ctrl
= getchar()) >= '\01' && ctrl
<= '\037') {
562 S(ctrl
); /* Insert the char */
566 error ("Unknown control character", NIL_PTR
);
570 * LIB insert a line at the current position and moves back to the end of
575 S('\n'); /* Insert the line */
576 UP(); /* Move one line up */
577 move_to(LINE_END
, y
); /* Move to end of this line */
581 * Line_insert() inserts a new line with text pointed to by `string'.
582 * It returns the address of the new line.
584 LINE
*line_insert(line
, string
, len
)
589 register LINE
*new_line
;
591 /* Allocate space for LINE structure and text */
592 new_line
= install_line(string
, len
);
594 /* Install the line into the double linked list */
595 new_line
->prev
= line
;
596 new_line
->next
= line
->next
;
597 line
->next
= new_line
;
598 new_line
->next
->prev
= new_line
;
600 /* Increment nlines */
607 * Insert() insert the string `string' at the given line and location.
609 int insert(line
, location
, string
)
611 char *location
, *string
;
613 register char *bufp
= text_buffer
; /* Buffer for building line */
614 register char *textp
= line
->text
;
616 if (length_of(textp
) + length_of(string
) >= MAX_CHARS
) {
617 error("Line too long", NIL_PTR
);
621 modified
= TRUE
; /* File has been modified */
623 /* Copy part of line until `location' has been reached */
624 while (textp
!= location
)
627 /* Insert string at this location */
628 while (*string
!= '\0')
632 if (*(string
- 1) == '\n') /* Insert a new line */
633 (void) line_insert(line
, location
, length_of(location
));
634 else /* Append last part of line */
635 copy_string(bufp
, location
);
637 /* Install the new text in this line */
638 free_space(line
->text
);
639 line
->text
= alloc(length_of(text_buffer
) + 1);
640 copy_string(line
->text
, text_buffer
);
646 * Line_delete() deletes the argument line out of the line list. The pointer to
647 * the next line is returned.
649 LINE
*line_delete(line
)
652 register LINE
*next_line
= line
->next
;
654 /* Delete the line */
655 line
->prev
->next
= line
->next
;
656 line
->next
->prev
= line
->prev
;
658 /* Free allocated space */
659 free_space(line
->text
);
660 free_space((char*)line
);
662 /* Decrement nlines */
669 * Delete() deletes all the characters (including newlines) between the
670 * startposition and endposition and fixes the screen accordingly. It
671 * returns the number of lines deleted.
673 void delete(start_line
, start_textp
, end_line
, end_textp
)
674 register LINE
*start_line
;
676 char *start_textp
, *end_textp
;
678 register char *textp
= start_line
->text
;
679 register char *bufp
= text_buffer
; /* Storage for new line->text */
681 int line_cnt
= 0; /* Nr of lines deleted */
683 int shift
= 0; /* Used in shift calculation */
686 modified
= TRUE
; /* File has been modified */
688 /* Set up new line. Copy first part of start line until start_position. */
689 while (textp
< start_textp
) {
694 /* Check if line doesn't exceed MAX_CHARS */
695 if (count
+ length_of(end_textp
) >= MAX_CHARS
) {
696 error("Line too long", NIL_PTR
);
700 /* Copy last part of end_line if end_line is not tail */
701 copy_string(bufp
, (end_textp
!= NIL_PTR
) ? end_textp
: "\n");
703 /* Delete all lines between start and end_position (including end_line) */
704 line
= start_line
->next
;
705 stop
= end_line
->next
;
706 while (line
!= stop
&& line
!= tail
) {
707 line
= line_delete(line
);
711 /* Check if last line of file should be deleted */
712 if (end_textp
== NIL_PTR
&& length_of(start_line
->text
) == 1 && nlines
> 1) {
713 start_line
= start_line
->prev
;
714 (void) line_delete(start_line
->next
);
717 else { /* Install new text */
718 free_space(start_line
->text
);
719 start_line
->text
= alloc(length_of(text_buffer
) + 1);
720 copy_string(start_line
->text
, text_buffer
);
723 /* Fix screen. First check if line is shifted. Perhaps we should shift it back*/
724 if (get_shift(start_line
->shift_count
)) {
725 shift
= (XBREAK
- count_chars(start_line
)) / SHIFT_SIZE
;
726 if (shift
> 0) { /* Shift line `shift' back */
727 if (shift
>= get_shift(start_line
->shift_count
))
728 start_line
->shift_count
= 0;
730 start_line
->shift_count
-= shift
;
731 nx
+= shift
* SHIFT_SIZE
;/* Reset x value */
735 if (line_cnt
== 0) { /* Check if only one line changed */
736 if (shift
> 0) { /* Reprint whole line */
738 line_print(start_line
);
740 else { /* Just display last part of line */
742 put_line(start_line
, x
, TRUE
);
744 move_to(nx
, y
); /* Reset cur_text */
748 shift
= last_y
; /* Save value */
750 display(0, y
, start_line
, shift
- y
);
751 move_to((line_cnt
== 1) ? nx
: 0, y
);
754 /* ======================================================================== *
756 * ======================================================================== */
758 LINE
*mark_line
; /* For marking position. */
760 int lines_saved
; /* Nr of lines in buffer */
763 * PT() inserts the buffer at the current location.
767 register int fd
; /* File descriptor for buffer */
769 if ((fd
= scratch_file(READ
)) == ERRORS
)
770 error("Buffer is empty.", NIL_PTR
);
772 file_insert(fd
, FALSE
);/* Insert the buffer */
778 * IF() prompt for a filename and inserts the file at the current location
783 register int fd
; /* File descriptor of file */
784 char name
[LINE_LEN
]; /* Buffer for file name */
786 /* Get the file name */
787 if (get_file("Get and insert file:", name
) != FINE
)
790 if ((fd
= open(name
, 0)) < 0)
791 error("Cannot open ", name
);
793 file_insert(fd
, TRUE
); /* Insert the file */
799 * File_insert() inserts a an opened file (as given by filedescriptor fd)
800 * at the current location.
802 void file_insert(fd
, old_pos
)
806 char line_buffer
[MAX_CHARS
]; /* Buffer for next line */
807 register LINE
*line
= cur_line
;
808 register int line_count
= nlines
; /* Nr of lines inserted */
809 LINE
*page
= cur_line
;
812 /* Get the first piece of text (might be ended with a '\n') from fd */
813 if (get_line(fd
, line_buffer
) == ERRORS
)
814 return; /* Empty file */
816 /* Insert this text at the current location. */
817 if (insert(line
, cur_text
, line_buffer
) == ERRORS
)
820 /* Repeat getting lines (and inserting lines) until EOF is reached */
821 while ((ret
= get_line(fd
, line_buffer
)) != ERRORS
&& ret
!= NO_LINE
)
822 line
= line_insert(line
, line_buffer
, ret
);
824 if (ret
== NO_LINE
) { /* Last line read not ended by a '\n' */
826 (void) insert(line
, line
->text
, line_buffer
);
829 /* Calculate nr of lines added */
830 line_count
= nlines
- line_count
;
833 if (line_count
== 0) { /* Only one line changed */
836 move_to((old_pos
== TRUE
) ? x
: x
+ length_of(line_buffer
), y
);
838 else { /* Several lines changed */
839 reset(top_line
, y
); /* Reset pointers */
840 while (page
!= line
&& page
!= bot_line
->next
)
842 if (page
!= bot_line
->next
|| old_pos
== TRUE
)
843 display(0, y
, cur_line
, screenmax
- y
);
846 else if (ret
== NO_LINE
)
847 move_to(length_of(line_buffer
), find_y(line
));
849 move_to(0, find_y(line
->next
));
852 /* If nr of added line >= REPORT, print the count */
853 if (line_count
>= REPORT
)
854 status_line(num_out((long) line_count
), " lines added.");
858 * WB() writes the buffer (yank_file) into another file, which
863 register int new_fd
; /* Filedescriptor to copy file */
864 int yank_fd
; /* Filedescriptor to buffer */
865 register int cnt
; /* Count check for read/write */
866 int ret
= 0; /* Error check for write */
867 char file
[LINE_LEN
]; /* Output file */
869 /* Checkout the buffer */
870 if ((yank_fd
= scratch_file(READ
)) == ERRORS
) {
871 error("Buffer is empty.", NIL_PTR
);
876 if (get_file("Write buffer to file:", file
) != FINE
)
879 /* Creat the new file */
880 if ((new_fd
= creat(file
, 0644)) < 0) {
881 error("Cannot create ", file
);
885 status_line("Writing ", file
);
887 /* Copy buffer into file */
888 while ((cnt
= read(yank_fd
, text_buffer
, sizeof(text_buffer
))) > 0)
889 if (write(new_fd
, text_buffer
, cnt
) != cnt
) {
895 /* Clean up open files and status_line */
896 (void) close(new_fd
);
897 (void) close(yank_fd
);
899 if (ret
!= ERRORS
) /* Bad write */
900 file_status("Wrote", chars_saved
, file
, lines_saved
, TRUE
, FALSE
);
904 * MA sets mark_line (mark_text) to the current line (text pointer).
908 mark_line
= cur_line
;
909 mark_text
= cur_text
;
910 status_line("Mark set", NIL_PTR
);
914 * YA() puts the text between the marked position and the current
923 * DT() is essentially the same as YA(), but in DT() the text is deleted.
931 * Set_up is an interface to the actual yank. It calls checkmark () to check
932 * if the marked position is still valid. If it is, yank is called with the
933 * arguments in the right order.
936 FLAG remove
; /* DELETE if text should be deleted */
938 switch (checkmark()) {
940 error("Mark not set.", NIL_PTR
);
943 yank(mark_line
, mark_text
, cur_line
, cur_text
, remove
);
946 yank(cur_line
, cur_text
, mark_line
, mark_text
, remove
);
948 case SAME
: /* Ignore stupid behaviour */
951 status_line("0 characters saved in buffer.", NIL_PTR
);
957 * Check_mark() checks if mark_line and mark_text are still valid pointers. If
958 * they are it returns SMALLER if the marked position is before the current,
959 * BIGGER if it isn't or SAME if somebody didn't get the point.
960 * NOT_VALID is returned when mark_line and/or mark_text are no longer valid.
961 * Legal() checks if mark_text is valid on the mark_line.
966 FLAG cur_seen
= FALSE
;
968 /* Special case: check is mark_line and cur_line are the same. */
969 if (mark_line
== cur_line
) {
970 if (mark_text
== cur_text
) /* Even same place */
972 if (legal() == ERRORS
) /* mark_text out of range */
974 return (mark_text
< cur_text
) ? SMALLER
: BIGGER
;
977 /* Start looking for mark_line in the line structure */
978 for (line
= header
->next
; line
!= tail
; line
= line
->next
) {
979 if (line
== cur_line
)
981 else if (line
== mark_line
)
985 /* If we found mark_line (line != tail) check for legality of mark_text */
986 if (line
== tail
|| legal() == ERRORS
)
989 /* cur_seen is TRUE if cur_line is before mark_line */
990 return (cur_seen
== TRUE
) ? BIGGER
: SMALLER
;
994 * Legal() checks if mark_text is still a valid pointer.
998 register char *textp
= mark_line
->text
;
1000 /* Locate mark_text on mark_line */
1001 while (textp
!= mark_text
&& *textp
++ != '\0')
1003 return (*textp
== '\0') ? ERRORS
: FINE
;
1007 * Yank puts all the text between start_position and end_position into
1009 * The caller must check that the arguments to yank() are valid. (E.g. in
1012 void yank(start_line
, start_textp
, end_line
, end_textp
, remove
)
1013 LINE
*start_line
, *end_line
;
1014 char *start_textp
, *end_textp
;
1015 FLAG remove
; /* DELETE if text should be deleted */
1017 register LINE
*line
= start_line
;
1018 register char *textp
= start_textp
;
1021 /* Creat file to hold buffer */
1022 if ((fd
= scratch_file(WRITE
)) == ERRORS
)
1027 status_line("Saving text.", NIL_PTR
);
1029 /* Keep writing chars until the end_location is reached. */
1030 while (textp
!= end_textp
) {
1031 if (write_char(fd
, *textp
) == ERRORS
) {
1035 if (*textp
++ == '\n') { /* Move to the next line */
1043 /* Flush the I/O buffer and close file */
1044 if (flush_buffer(fd
) == ERRORS
) {
1049 yank_status
= VALID
;
1052 * Check if the text should be deleted as well. If it should, the following
1053 * hack is used to save a lot of code. First move back to the start_position.
1054 * (This might be the location we're on now!) and them delete the text.
1055 * It might be a bit confusing the first time somebody uses it.
1056 * Delete() will fix the screen.
1058 if (remove
== DELETE
) {
1059 move_to(find_x(start_line
, start_textp
), find_y(start_line
));
1060 delete(start_line
, start_textp
, end_line
, end_textp
);
1063 status_line(num_out(chars_saved
), " characters saved in buffer.");
1067 * Scratch_file() creates a uniq file in /usr/tmp. If the file couldn't
1068 * be created other combinations of files are tried until a maximum
1069 * of MAXTRAILS times. After MAXTRAILS times, an error message is given
1070 * and ERRORS is returned.
1073 #define MAXTRAILS 26
1075 int scratch_file(mode
)
1076 FLAG mode
; /* Can be READ or WRITE permission */
1078 static int trials
= 0; /* Keep track of trails */
1079 register char *y_ptr
, *n_ptr
;
1080 int fd
; /* Filedescriptor to buffer */
1082 /* If yank_status == NOT_VALID, scratch_file is called for the first time */
1083 if (yank_status
== NOT_VALID
&& mode
== WRITE
) { /* Create new file */
1084 /* Generate file name. */
1085 y_ptr
= &yank_file
[11];
1086 n_ptr
= num_out((long) getpid());
1087 while ((*y_ptr
= *n_ptr
++) != '\0')
1089 *y_ptr
++ = 'a' + trials
;
1091 /* Check file existence */
1092 if (access(yank_file
, 0) == 0 || (fd
= creat(yank_file
, 0644)) < 0) {
1093 if (trials
++ >= MAXTRAILS
) {
1094 error("Unable to creat scratchfile.", NIL_PTR
);
1098 return scratch_file(mode
);/* Have another go */
1101 else if ((mode
== READ
&& (fd
= open(yank_file
, 0)) < 0) ||
1102 (mode
== WRITE
&& (fd
= creat(yank_file
, 0644)) < 0)) {
1103 yank_status
= NOT_VALID
;
1111 /* ======================================================================== *
1113 * ======================================================================== */
1116 * A regular expression consists of a sequence of:
1117 * 1. A normal character matching that character.
1118 * 2. A . matching any character.
1119 * 3. A ^ matching the begin of a line.
1120 * 4. A $ (as last character of the pattern) mathing the end of a line.
1121 * 5. A \<character> matching <character>.
1122 * 6. A number of characters enclosed in [] pairs matching any of these
1123 * characters. A list of characters can be indicated by a '-'. So
1124 * [a-z] matches any letter of the alphabet. If the first character
1125 * after the '[' is a '^' then the set is negated (matching none of
1127 * A ']', '^' or '-' can be escaped by putting a '\' in front of it.
1128 * 7. If one of the expressions as described in 1-6 is followed by a
1129 * '*' than that expressions matches a sequence of 0 or more of
1133 char typed_expression
[LINE_LEN
]; /* Holds previous expr. */
1136 * SF searches forward for an expression.
1140 search("Search forward:", FORWARD
);
1144 * SF searches backwards for an expression.
1148 search("Search reverse:", REVERSE
);
1152 * Get_expression() prompts for an expression. If just a return is typed, the
1153 * old expression is used. If the expression changed, compile() is called and
1154 * the returning REGEX structure is returned. It returns NIL_REG upon error.
1155 * The save flag indicates whether the expression should be appended at the
1158 REGEX
*get_expression(message
)
1161 static REGEX program
; /* Program of expression */
1162 char exp_buf
[LINE_LEN
]; /* Buffer for new expr. */
1164 if (get_string(message
, exp_buf
, FALSE
) == ERRORS
)
1167 if (exp_buf
[0] == '\0' && typed_expression
[0] == '\0') {
1168 error("No previous expression.", NIL_PTR
);
1172 if (exp_buf
[0] != '\0') { /* A new expr. is typed */
1173 copy_string(typed_expression
, exp_buf
);/* Save expr. */
1174 compile(exp_buf
, &program
); /* Compile new expression */
1177 if (program
.status
== REG_ERROR
) { /* Error during compiling */
1178 error(program
.result
.err_mess
, NIL_PTR
);
1185 * GR() a replaces all matches from the current position until the end
1190 change("Global replace:", VALID
);
1194 * LR() replaces all matches on the current line.
1198 change("Line replace:", NOT_VALID
);
1202 * Change() prompts for an expression and a substitution pattern and changes
1203 * all matches of the expression into the substitution. change() start looking
1204 * for expressions at the current line and continues until the end of the file
1205 * if the FLAG file is VALID.
1207 void change(message
, file
)
1208 char *message
; /* Message to prompt for expression */
1211 char mess_buf
[LINE_LEN
]; /* Buffer to hold message */
1212 char replacement
[LINE_LEN
]; /* Buffer to hold subst. pattern */
1213 REGEX
*program
; /* Program resulting from compilation */
1214 register LINE
*line
= cur_line
;
1215 register char *textp
;
1216 long lines
= 0L; /* Nr of lines on which subs occurred */
1217 long subs
= 0L; /* Nr of subs made */
1218 int page
= y
; /* Index to check if line is on screen*/
1220 /* Save message and get expression */
1221 copy_string(mess_buf
, message
);
1222 if ((program
= get_expression(mess_buf
)) == NIL_REG
)
1225 /* Get substitution pattern */
1226 build_string(mess_buf
, "%s %s by:", mess_buf
, typed_expression
);
1227 if (get_string(mess_buf
, replacement
, FALSE
) == ERRORS
)
1230 set_cursor(0, ymax
);
1232 /* Substitute until end of file */
1234 if (line_check(program
, line
->text
, FORWARD
)) {
1236 /* Repeat sub. on this line as long as we find a match*/
1238 subs
++; /* Increment subs */
1239 if ((textp
= substitute(line
, program
,replacement
))
1241 return; /* Line too long */
1242 } while ((program
->status
& BEGIN_LINE
) != BEGIN_LINE
&&
1243 (program
->status
& END_LINE
) != END_LINE
&&
1244 line_check(program
, textp
, FORWARD
));
1245 /* Check to see if we can print the result */
1246 if (page
<= screenmax
) {
1247 set_cursor(0, page
);
1251 if (page
<= screenmax
)
1254 } while (line
!= tail
&& file
== VALID
&& quit
== FALSE
);
1256 copy_string(mess_buf
, (quit
== TRUE
) ? "(Aborted) " : "");
1257 /* Fix the status line */
1258 if (subs
== 0L && quit
== FALSE
)
1259 error("Pattern not found.", NIL_PTR
);
1260 else if (lines
>= REPORT
|| quit
== TRUE
) {
1261 build_string(mess_buf
, "%s %D substitutions on %D lines.", mess_buf
,
1263 status_line(mess_buf
, NIL_PTR
);
1265 else if (file
== NOT_VALID
&& subs
>= REPORT
)
1266 status_line(num_out(subs
), " substitutions.");
1273 * Substitute() replaces the match on this line by the substitute pattern
1274 * as indicated by the program. Every '&' in the replacement is replaced by
1275 * the original match. A \ in the replacement escapes the next character.
1277 char *substitute(line
, program
, replacement
)
1280 char *replacement
; /* Contains replacement pattern */
1282 register char *textp
= text_buffer
;
1283 register char *subp
= replacement
;
1284 char *linep
= line
->text
;
1289 /* Copy part of line until the beginning of the match */
1290 while (linep
!= program
->start_ptr
)
1291 *textp
++ = *linep
++;
1294 * Replace the match by the substitution pattern. Each occurrence of '&' is
1295 * replaced by the original match. A \ escapes the next character.
1297 while (*subp
!= '\0' && textp
< &text_buffer
[MAX_CHARS
]) {
1298 if (*subp
== '&') { /* Replace the original match */
1299 amp
= program
->start_ptr
;
1300 while (amp
< program
->end_ptr
&& textp
<&text_buffer
[MAX_CHARS
])
1305 if (*subp
== '\\' && *(subp
+ 1) != '\0')
1311 /* Check for line length not exceeding MAX_CHARS */
1312 if (length_of(text_buffer
) + length_of(program
->end_ptr
) >= MAX_CHARS
) {
1313 error("Substitution result: line too big", NIL_PTR
);
1317 /* Append last part of line to the new build line */
1318 copy_string(textp
, program
->end_ptr
);
1320 /* Free old line and install new one */
1321 free_space(line
->text
);
1322 line
->text
= alloc(length_of(text_buffer
) + 1);
1323 copy_string(line
->text
, text_buffer
);
1325 return(line
->text
+ (textp
- text_buffer
));
1329 * Search() calls get_expression to fetch the expression. If this went well,
1330 * the function match() is called which returns the line with the next match.
1331 * If this line is the NIL_LINE, it means that a match could not be found.
1332 * Find_x() and find_y() display the right page on the screen, and return
1333 * the right coordinates for x and y. These coordinates are passed to move_to()
1335 void search(message
, method
)
1339 register REGEX
*program
;
1340 register LINE
*match_line
;
1342 /* Get the expression */
1343 if ((program
= get_expression(message
)) == NIL_REG
)
1346 set_cursor(0, ymax
);
1348 /* Find the match */
1349 if ((match_line
= match(program
, cur_text
, method
)) == NIL_LINE
) {
1351 status_line("Aborted", NIL_PTR
);
1353 status_line("Pattern not found.", NIL_PTR
);
1357 move(0, program
->start_ptr
, find_y(match_line
));
1362 * find_y() checks if the matched line is on the current page. If it is, it
1363 * returns the new y coordinate, else it displays the correct page with the
1364 * matched line in the middle and returns the new y value;
1366 int find_y(match_line
)
1369 register LINE
*line
;
1370 register int count
= 0;
1372 /* Check if match_line is on the same page as currently displayed. */
1373 for (line
= top_line
; line
!= match_line
&& line
!= bot_line
->next
;
1376 if (line
!= bot_line
->next
)
1379 /* Display new page, with match_line in center. */
1380 if ((line
= proceed(match_line
, -(screenmax
>> 1))) == header
) {
1381 /* Can't display in the middle. Make first line of file top_line */
1383 for (line
= header
->next
; line
!= match_line
; line
= line
->next
)
1385 line
= header
->next
;
1387 else /* New page is displayed. Set cursor to middle of page */
1388 count
= screenmax
>> 1;
1390 /* Reset pointers and redraw the screen */
1397 /* Opcodes for characters */
1398 #define NORMAL 0x0200
1402 #define BRACKET 0x2000
1403 #define NEGATE 0x0100
1406 /* Mask for opcodes and characters */
1407 #define LOW_BYTE 0x00FF
1408 #define HIGH_BYTE 0xFF00
1410 /* Previous is the contents of the previous address (ptr) points to */
1411 #define previous(ptr) (*((ptr) - 1))
1413 /* Buffer to store outcome of compilation */
1414 int exp_buffer
[BLOCK_SIZE
];
1416 /* Errors often used */
1417 char *too_long
= "Regular expression too long";
1420 * Reg_error() is called by compile() is something went wrong. It set the
1421 * status of the structure to error, and assigns the error field of the union.
1423 #define reg_error(str) program->status = REG_ERROR, \
1424 program->result.err_mess = (str)
1426 * Finished() is called when everything went right during compilation. It
1427 * allocates space for the expression, and copies the expression buffer into
1430 void finished(program
, last_exp
)
1431 register REGEX
*program
;
1434 register int length
= (last_exp
- exp_buffer
) * sizeof(int);
1436 /* Allocate space */
1437 program
->result
.expression
= (int *) alloc(length
);
1438 /* Copy expression. (expression consists of ints!) */
1439 bcopy(exp_buffer
, program
->result
.expression
, length
);
1443 * Compile compiles the pattern into a more comprehensible form and returns a
1444 * REGEX structure. If something went wrong, the status field of the structure
1445 * is set to REG_ERROR and an error message is set into the err_mess field of
1446 * the union. If all went well the expression is saved and the expression
1447 * pointer is set to the saved (and compiled) expression.
1449 void compile(pattern
, program
)
1450 register char *pattern
; /* Pointer to pattern */
1453 register int *expression
= exp_buffer
;
1454 int *prev_char
; /* Pointer to previous compiled atom */
1455 int *acct_field
; /* Pointer to last BRACKET start */
1456 FLAG negate
; /* Negate flag for BRACKET */
1457 char low_char
; /* Index for chars in BRACKET */
1460 /* Check for begin of line */
1461 if (*pattern
== '^') {
1462 program
->status
= BEGIN_LINE
;
1466 program
->status
= 0;
1467 /* If the first character is a '*' we have to assign it here. */
1468 if (*pattern
== '*') {
1469 *expression
++ = '*' + NORMAL
;
1475 switch (c
= *pattern
++) {
1477 *expression
++ = DOT
;
1481 * Only means EOLN if it is the last char of the pattern
1483 if (*pattern
== '\0') {
1484 *expression
++ = EOLN
| DONE
;
1485 program
->status
|= END_LINE
;
1486 finished(program
, expression
);
1490 *expression
++ = NORMAL
+ '$';
1493 *expression
++ = DONE
;
1494 finished(program
, expression
);
1497 /* If last char, it must! mean a normal '\' */
1498 if (*pattern
== '\0')
1499 *expression
++ = NORMAL
+ '\\';
1501 *expression
++ = NORMAL
+ *pattern
++;
1505 * If the previous expression was a [] find out the
1506 * begin of the list, and adjust the opcode.
1508 prev_char
= expression
- 1;
1509 if (*prev_char
& BRACKET
)
1510 *(expression
- (*acct_field
& LOW_BYTE
))|= STAR
;
1516 * First field in expression gives information about
1518 * The opcode consists of BRACKET and if necessary
1519 * NEGATE to indicate that the list should be negated
1520 * and/or STAR to indicate a number of sequence of this
1522 * The lower byte contains the length of the list.
1524 acct_field
= expression
++;
1525 if (*pattern
== '^') { /* List must be negated */
1531 while (*pattern
!= ']') {
1532 if (*pattern
== '\0') {
1533 reg_error("Missing ]");
1536 if (*pattern
== '\\')
1538 *expression
++ = *pattern
++;
1539 if (*pattern
== '-') {
1540 /* Make list of chars */
1541 low_char
= previous(pattern
);
1542 pattern
++; /* Skip '-' */
1543 if (low_char
++ > *pattern
) {
1544 reg_error("Bad range in [a-z]");
1548 while (low_char
<= *pattern
)
1549 *expression
++ = low_char
++;
1552 if (expression
>= &exp_buffer
[BLOCK_SIZE
]) {
1553 reg_error(too_long
);
1557 pattern
++; /* Skip ']' */
1558 /* Assign length of list in acct field */
1559 if ((*acct_field
= (expression
- acct_field
)) == 1) {
1560 reg_error("Empty []");
1563 /* Assign negate and bracket field */
1564 *acct_field
|= BRACKET
;
1566 *acct_field
|= NEGATE
;
1568 * Add BRACKET to opcode of last char in field because
1569 * a '*' may be following the list.
1571 previous(expression
) |= BRACKET
;
1574 *expression
++ = c
+ NORMAL
;
1576 if (expression
== &exp_buffer
[BLOCK_SIZE
]) {
1577 reg_error(too_long
);
1585 * Match gets as argument the program, pointer to place in current line to
1586 * start from and the method to search for (either FORWARD or REVERSE).
1587 * Match() will look through the whole file until a match is found.
1588 * NIL_LINE is returned if no match could be found.
1590 LINE
*match(program
, string
, method
)
1593 register FLAG method
;
1595 register LINE
*line
= cur_line
;
1596 char old_char
; /* For saving chars */
1598 /* Corrupted program */
1599 if (program
->status
== REG_ERROR
)
1602 /* Check part of text first */
1603 if (!(program
->status
& BEGIN_LINE
)) {
1604 if (method
== FORWARD
) {
1605 if (line_check(program
, string
+ 1, method
) == MATCH
)
1606 return cur_line
; /* Match found */
1608 else if (!(program
->status
& END_LINE
)) {
1609 old_char
= *string
; /* Save char and */
1610 *string
= '\n'; /* Assign '\n' for line_check */
1611 if (line_check(program
, line
->text
, method
) == MATCH
) {
1612 *string
= old_char
; /* Restore char */
1613 return cur_line
; /* Found match */
1615 *string
= old_char
; /* No match, but restore char */
1619 /* No match in last (or first) part of line. Check out rest of file */
1621 line
= (method
== FORWARD
) ? line
->next
: line
->prev
;
1622 if (line
->text
== NIL_PTR
) /* Header/tail */
1624 if (line_check(program
, line
->text
, method
) == MATCH
)
1626 } while (line
!= cur_line
&& quit
== FALSE
);
1628 /* No match found. */
1633 * Line_check() checks the line (or rather string) for a match. Method
1634 * indicates FORWARD or REVERSE search. It scans through the whole string
1635 * until a match is found, or the end of the string is reached.
1637 int line_check(program
, string
, method
)
1638 register REGEX
*program
;
1642 register char *textp
= string
;
1644 /* Assign start_ptr field. We might find a match right away! */
1645 program
->start_ptr
= textp
;
1647 /* If the match must be anchored, just check the string. */
1648 if (program
->status
& BEGIN_LINE
)
1649 return check_string(program
, string
, NIL_INT
);
1651 if (method
== REVERSE
) {
1652 /* First move to the end of the string */
1653 for (textp
= string
; *textp
!= '\n'; textp
++)
1655 /* Start checking string until the begin of the string is met */
1656 while (textp
>= string
) {
1657 program
->start_ptr
= textp
;
1658 if (check_string(program
, textp
--, NIL_INT
))
1663 /* Move through the string until the end of is found */
1664 while (quit
== FALSE
&& *textp
!= '\0') {
1665 program
->start_ptr
= textp
;
1666 if (check_string(program
, textp
, NIL_INT
))
1678 * Check() checks of a match can be found in the given string. Whenever a STAR
1679 * is found during matching, then the begin position of the string is marked
1680 * and the maximum number of matches is performed. Then the function star()
1681 * is called which starts to finish the match from this position of the string
1682 * (and expression). Check() return MATCH for a match, NO_MATCH is the string
1683 * couldn't be matched or REG_ERROR for an illegal opcode in expression.
1685 int check_string(program
, string
, expression
)
1687 register char *string
;
1690 register int opcode
; /* Holds opcode of next expr. atom */
1691 char c
; /* Char that must be matched */
1692 char *mark
; /* For marking position */
1693 int star_fl
; /* A star has been born */
1695 if (expression
== NIL_INT
)
1696 expression
= program
->result
.expression
;
1698 /* Loop until end of string or end of expression */
1699 while (quit
== FALSE
&& !(*expression
& DONE
) &&
1700 *string
!= '\0' && *string
!= '\n') {
1701 c
= *expression
& LOW_BYTE
; /* Extract match char */
1702 opcode
= *expression
& HIGH_BYTE
; /* Extract opcode */
1703 if (star_fl
= (opcode
& STAR
)) { /* Check star occurrence */
1704 opcode
&= ~STAR
; /* Strip opcode */
1705 mark
= string
; /* Mark current position */
1707 expression
++; /* Increment expr. */
1711 while (*string
++ == c
) /* Skip all matches */
1713 else if (*string
++ != c
)
1718 if (star_fl
) /* Skip to eoln */
1719 while (*string
!= '\0' && *string
++ != '\n')
1722 case NEGATE
| BRACKET
:
1725 while (in_list(expression
, *string
++, c
, opcode
)
1728 else if (in_list(expression
, *string
++, c
, opcode
) == NO_MATCH
)
1730 expression
+= c
- 1; /* Add length of list */
1733 panic("Corrupted program in check_string()");
1736 return star(program
, mark
, string
, expression
);
1738 if (*expression
& DONE
) {
1739 program
->end_ptr
= string
; /* Match ends here */
1741 * We might have found a match. The last thing to do is check
1742 * whether a '$' was given at the end of the expression, or
1743 * the match was found on a null string. (E.g. [a-z]* always
1744 * matches) unless a ^ or $ was included in the pattern.
1746 if ((*expression
& EOLN
) && *string
!= '\n' && *string
!= '\0')
1748 if (string
== program
->start_ptr
&& !(program
->status
& BEGIN_LINE
)
1749 && !(*expression
& EOLN
))
1757 * Star() calls check_string() to find out the longest match possible.
1758 * It searches backwards until the (in check_string()) marked position
1759 * is reached, or a match is found.
1761 int star(program
, end_position
, string
, expression
)
1763 register char *end_position
;
1764 register char *string
;
1769 if (check_string(program
, string
, expression
))
1771 } while (string
!= end_position
);
1777 * In_list() checks if the given character is in the list of []. If it is
1778 * it returns MATCH. if it isn't it returns NO_MATCH. These returns values
1779 * are reversed when the NEGATE field in the opcode is present.
1781 int in_list(list
, c
, list_length
, opcode
)
1784 register int list_length
;
1787 if (c
== '\0' || c
== '\n') /* End of string, never matches */
1789 while (list_length
-- > 1) { /* > 1, don't check acct_field */
1790 if ((*list
& LOW_BYTE
) == c
)
1791 return (opcode
& NEGATE
) ? NO_MATCH
: MATCH
;
1794 return (opcode
& NEGATE
) ? MATCH
: NO_MATCH
;
1798 * Dummy_line() adds an empty line at the end of the file. This is sometimes
1799 * useful in combination with the EF and DN command in combination with the
1804 (void) line_insert(tail
->prev
, "\n", 1);
1805 tail
->prev
->shift_count
= DUMMY
;
1806 if (last_y
!= screenmax
) {
1808 bot_line
= bot_line
->next
;