1725 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1725 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
|  * Part 2 of the mined editor.
 | |
|  */
 | |
| 
 | |
| /*  ========================================================================  *
 | |
|  *				Move Commands				      *	
 | |
|  *  ========================================================================  */
 | |
| 
 | |
| #include "mined.h"
 | |
| #include <string.h>
 | |
| 
 | |
| /*
 | |
|  * Move one line up.
 | |
|  */
 | |
| void UP()
 | |
| {
 | |
|   if (y == 0) {		/* Top line of screen. Scroll one line */
 | |
|   	(void) reverse_scroll();
 | |
|   	move_to(x, y);
 | |
|   }
 | |
|   else			/* Move to previous line */
 | |
|   	move_to(x, y - 1);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Move one line down.
 | |
|  */
 | |
| void DN()
 | |
| {
 | |
|   if (y == last_y) {	/* Last line of screen. Scroll one line */
 | |
| 	if (bot_line->next == tail && bot_line->text[0] != '\n') {
 | |
| 		dummy_line();		/* Create new empty line */
 | |
| 		DN();
 | |
| 		return;
 | |
| 	}
 | |
| 	else {
 | |
| 		(void) forward_scroll();
 | |
| 		move_to(x, y);
 | |
| 	}
 | |
|   }
 | |
|   else			/* Move to next line */
 | |
|   	move_to(x, y + 1);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Move left one position.
 | |
|  */
 | |
| void LF()
 | |
| {
 | |
|   if (x == 0 && get_shift(cur_line->shift_count) == 0) {/* Begin of line */
 | |
| 	if (cur_line->prev != header) {
 | |
| 		UP();					/* Move one line up */
 | |
| 		move_to(LINE_END, y);
 | |
| 	}
 | |
|   }
 | |
|   else
 | |
|   	move_to(x - 1, y);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Move right one position.
 | |
|  */
 | |
| void RT()
 | |
| {
 | |
|   if (*cur_text == '\n') {
 | |
|   	if (cur_line->next != tail) {		/* Last char of file */
 | |
| 		DN();				/* Move one line down */
 | |
| 		move_to(LINE_START, y);
 | |
| 	}
 | |
|   }
 | |
|   else
 | |
|   	move_to(x + 1, y);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Move to coordinates [0, 0] on screen.
 | |
|  */
 | |
| void HIGH()
 | |
| {
 | |
|   move_to(0, 0);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Move to coordinates [0, YMAX] on screen.
 | |
|  */
 | |
| void LOW()
 | |
| {
 | |
|   move_to(0, last_y);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Move to begin of line.
 | |
|  */
 | |
| void BL()
 | |
| {
 | |
|   move_to(LINE_START, y);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Move to end of line.
 | |
|  */
 | |
| void EL()
 | |
| {
 | |
|   move_to(LINE_END, y);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * GOTO() prompts for a linenumber and moves to that line.
 | |
|  */
 | |
| void GOTO()
 | |
| {
 | |
|   int number;
 | |
|   LINE *line;
 | |
| 
 | |
|   if (get_number("Please enter line number.", &number) == ERRORS)
 | |
|   	return;
 | |
| 
 | |
|   if (number <= 0 || (line = proceed(header->next, number - 1)) == tail)
 | |
|   	error("Illegal line number: ", num_out((long) number));
 | |
|   else
 | |
|   	move_to(x, find_y(line));
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Scroll forward one page or to eof, whatever comes first. (Bot_line becomes 
 | |
|  * top_line of display.) Try to leave the cursor on the same line. If this is
 | |
|  * not possible, leave cursor on the line halfway the page.
 | |
|  */
 | |
| void PD()
 | |
| {
 | |
|   register int i;
 | |
| 
 | |
|   for (i = 0; i < screenmax; i++)
 | |
|   	if (forward_scroll() == ERRORS)
 | |
|   		break;			/* EOF reached */
 | |
|   if (y - i < 0)				/* Line no longer on screen */
 | |
|   	move_to(0, screenmax >> 1);
 | |
|   else
 | |
|   	move_to(0, y - i);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Scroll backwards one page or to top of file, whatever comes first. (Top_line
 | |
|  * becomes bot_line of display).  The very bottom line (YMAX) is always blank.
 | |
|  * Try to leave the cursor on the same line. If this is not possible, leave
 | |
|  * cursor on the line halfway the page.
 | |
|  */
 | |
| void PU()
 | |
| {
 | |
|   register int i;
 | |
| 
 | |
|   for (i = 0; i < screenmax; i++)
 | |
|   	if (reverse_scroll() == ERRORS)
 | |
|   		break;			/* Top of file reached */
 | |
|   set_cursor(0, ymax);			/* Erase very bottom line */
 | |
| #ifdef UNIX
 | |
|   tputs(CE, 0, _putchar);
 | |
| #else
 | |
|   string_print(blank_line);
 | |
| #endif /* UNIX */
 | |
|   if (y + i > screenmax)			/* line no longer on screen */
 | |
|   	move_to(0, screenmax >> 1);
 | |
|   else
 | |
|   	move_to(0, y + i);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Go to top of file, scrolling if possible, else redrawing screen.
 | |
|  */
 | |
| void HO()
 | |
| {
 | |
|   if (proceed(top_line, -screenmax) == header)
 | |
|   	PU();			/* It fits. Let PU do it */
 | |
|   else {
 | |
|   	reset(header->next, 0);/* Reset top_line, etc. */
 | |
|   	RD();			/* Display full page */
 | |
|   }
 | |
|   move_to(LINE_START, 0);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Go to last line of file, scrolling if possible, else redrawing screen
 | |
|  */
 | |
| void EF()
 | |
| {
 | |
|   if (tail->prev->text[0] != '\n')
 | |
| 	dummy_line();
 | |
|   if (proceed(bot_line, screenmax) == tail)
 | |
|   	PD();			/* It fits. Let PD do it */
 | |
|   else {
 | |
|   	reset(proceed(tail->prev, -screenmax), screenmax);
 | |
|   	RD();			/* Display full page */
 | |
|   }
 | |
|   move_to(LINE_START, last_y);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Scroll one line up. Leave the cursor on the same line (if possible).
 | |
|  */
 | |
| void SU()
 | |
| {
 | |
|   if (top_line->prev == header)	/* Top of file. Can't scroll */
 | |
|   	return;
 | |
| 
 | |
|   (void) reverse_scroll();
 | |
|   set_cursor(0, ymax);		/* Erase very bottom line */
 | |
| #ifdef UNIX
 | |
|   tputs(CE, 0, _putchar);
 | |
| #else
 | |
|   string_print(blank_line);
 | |
| #endif /* UNIX */
 | |
|   move_to(x, (y == screenmax) ? screenmax : y + 1);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Scroll one line down. Leave the cursor on the same line (if possible).
 | |
|  */
 | |
| void SD()
 | |
| {
 | |
|   if (forward_scroll() != ERRORS) 
 | |
|   	move_to(x, (y == 0) ? 0 : y - 1);
 | |
|   else
 | |
|   	set_cursor(x, y);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Perform a forward scroll. It returns ERRORS if we're at the last line of the
 | |
|  * file.
 | |
|  */
 | |
| int forward_scroll()
 | |
| {
 | |
|   if (bot_line->next == tail)		/* Last line of file. No dice */
 | |
|   	return ERRORS;
 | |
|   top_line = top_line->next;
 | |
|   bot_line = bot_line->next;
 | |
|   cur_line = cur_line->next;
 | |
|   set_cursor(0, ymax);
 | |
|   line_print(bot_line);
 | |
| 
 | |
|   return FINE;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Perform a backwards scroll. It returns ERRORS if we're at the first line
 | |
|  * of the file.
 | |
|  */
 | |
| int reverse_scroll()
 | |
| {
 | |
|   if (top_line->prev == header)
 | |
|   	return ERRORS;		/* Top of file. Can't scroll */
 | |
| 
 | |
|   if (last_y != screenmax)	/* Reset last_y if necessary */
 | |
|   	last_y++;
 | |
|   else
 | |
|   	bot_line = bot_line->prev;	/* Else adjust bot_line */
 | |
|   top_line = top_line->prev;
 | |
|   cur_line = cur_line->prev;
 | |
| 
 | |
| /* Perform the scroll */
 | |
|   set_cursor(0, 0);
 | |
| #ifdef UNIX
 | |
|   tputs(AL, 0, _putchar);
 | |
| #else
 | |
|   string_print(rev_scroll);
 | |
| #endif /* UNIX */
 | |
|   set_cursor(0, 0);
 | |
|   line_print(top_line);
 | |
| 
 | |
|   return FINE;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * A word is defined as a number of non-blank characters separated by tabs
 | |
|  * spaces or linefeeds.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * MP() moves to the start of the previous word. A word is defined as a
 | |
|  * number of non-blank characters separated by tabs spaces or linefeeds.
 | |
|  */
 | |
| void MP()
 | |
| {
 | |
|   move_previous_word(NO_DELETE);
 | |
| }
 | |
| 
 | |
| void move_previous_word(remove)
 | |
| FLAG remove;
 | |
| {
 | |
|   register char *begin_line;
 | |
|   register char *textp;
 | |
|   char start_char = *cur_text;
 | |
|   char *start_pos = cur_text;
 | |
| 
 | |
| /* Fist check if we're at the beginning of line. */
 | |
|   if (cur_text == cur_line->text) {
 | |
|   	if (cur_line->prev == header)
 | |
|   		return;
 | |
|   	start_char = '\0';
 | |
|   }
 | |
| 
 | |
|   LF();
 | |
| 
 | |
|   begin_line = cur_line->text;
 | |
|   textp = cur_text;
 | |
| 
 | |
| /* Check if we're in the middle of a word. */
 | |
|   if (!alpha(*textp) || !alpha(start_char)) {
 | |
|   	while (textp != begin_line && (white_space(*textp) || *textp == '\n'))
 | |
|   		textp--;
 | |
|   }
 | |
| 
 | |
| /* Now we're at the end of previous word. Skip non-blanks until a blank comes */
 | |
|   while (textp != begin_line && alpha(*textp))
 | |
|   	textp--;
 | |
| 
 | |
| /* Go to the next char if we're not at the beginning of the line */
 | |
|   if (textp != begin_line && *textp != '\n')
 | |
|   	textp++;
 | |
| 
 | |
| /* Find the x-coordinate of this address, and move to it */
 | |
|   move_address(textp);
 | |
|   if (remove == DELETE)
 | |
|   	delete(cur_line, textp, cur_line, start_pos);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * MN() moves to the start of the next word. A word is defined as a number of
 | |
|  * non-blank characters separated by tabs spaces or linefeeds. Always keep in
 | |
|  * mind that the pointer shouldn't pass the '\n'.
 | |
|  */
 | |
| void MN()
 | |
| {
 | |
|   move_next_word(NO_DELETE);
 | |
| }
 | |
| 
 | |
| void move_next_word(remove)
 | |
| FLAG remove;
 | |
| {
 | |
|   register char *textp = cur_text;
 | |
| 
 | |
| /* Move to the end of the current word. */
 | |
|   while (*textp != '\n' && alpha(*textp))
 | |
|   	textp++;
 | |
| 
 | |
| /* Skip all white spaces */
 | |
|   while (*textp != '\n' && white_space(*textp))
 | |
|   	textp++;
 | |
| /* If we're deleting. delete the text in between */
 | |
|   if (remove == DELETE) {
 | |
|   	delete(cur_line, cur_text, cur_line, textp);
 | |
|   	return;
 | |
|   }
 | |
| 
 | |
| /* If we're at end of line. move to the first word on the next line. */
 | |
|   if (*textp == '\n' && cur_line->next != tail) {
 | |
|   	DN();
 | |
|   	move_to(LINE_START, y);
 | |
|   	textp = cur_text;
 | |
|   	while (*textp != '\n' && white_space(*textp))
 | |
|   		textp++;
 | |
|   }
 | |
|   move_address(textp);
 | |
| }
 | |
| 
 | |
| /*  ========================================================================  *
 | |
|  *				Modify Commands				      *
 | |
|  *  ========================================================================  */
 | |
| 
 | |
| /*
 | |
|  * DCC deletes the character under the cursor.  If this character is a '\n' the
 | |
|  * current line is joined with the next one.
 | |
|  * If this character is the only character of the line, the current line will
 | |
|  * be deleted.
 | |
|  */
 | |
| void DCC()
 | |
| {
 | |
|   if (*cur_text == '\n')
 | |
|   	delete(cur_line,cur_text, cur_line->next,cur_line->next->text);
 | |
|   else
 | |
|   	delete(cur_line, cur_text, cur_line, cur_text + 1);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * DPC deletes the character on the left side of the cursor.  If the cursor is
 | |
|  * at the beginning of the line, the last character if the previous line is
 | |
|  * deleted. 
 | |
|  */
 | |
| void DPC()
 | |
| {
 | |
|   if (x == 0 && cur_line->prev == header)
 | |
|   	return;			/* Top of file */
 | |
|   
 | |
|   LF();				/* Move one left */
 | |
|   DCC();				/* Delete character under cursor */
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * DLN deletes all characters until the end of the line. If the current
 | |
|  * character is a '\n', then delete that char.
 | |
|  */
 | |
| void DLN()
 | |
| {
 | |
|   if (*cur_text == '\n')
 | |
|   	DCC();
 | |
|   else
 | |
|   	delete(cur_line, cur_text, cur_line, cur_text + length_of(cur_text) -1);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * DNW() deletes the next word (as described in MN())
 | |
|  */
 | |
| void DNW()
 | |
| {
 | |
|   if (*cur_text == '\n')
 | |
|   	DCC();
 | |
|   else
 | |
|   	move_next_word(DELETE);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * DPW() deletes the next word (as described in MP())
 | |
|  */
 | |
| void DPW()
 | |
| {
 | |
|   if (cur_text == cur_line->text)
 | |
|   	DPC();
 | |
|   else
 | |
|   	move_previous_word(DELETE);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Insert character `character' at current location.
 | |
|  */
 | |
| void S(character)
 | |
| register char character;
 | |
| {
 | |
|   static char buffer[2];
 | |
| 
 | |
|   buffer[0] = character;
 | |
| /* Insert the character */
 | |
|   if (insert(cur_line, cur_text, buffer) == ERRORS)
 | |
|   	return;
 | |
| 
 | |
| /* Fix screen */
 | |
|   if (character == '\n') {
 | |
|   	set_cursor(0, y);
 | |
|   	if (y == screenmax) {		/* Can't use display */
 | |
|   		line_print(cur_line);
 | |
|   		(void) forward_scroll();
 | |
|   	}
 | |
|   	else {
 | |
|   		reset(top_line, y);	/* Reset pointers */
 | |
|   		display(0, y, cur_line, last_y - y);
 | |
|   	}
 | |
|   	move_to(0, (y == screenmax) ? y : y + 1);
 | |
|   }
 | |
|   else if (x + 1 == XBREAK)/* If line must be shifted, just call move_to*/
 | |
|   	move_to(x + 1, y);
 | |
|   else {			 /* else display rest of line */
 | |
|   	put_line(cur_line, x, FALSE);
 | |
|   	move_to(x + 1, y);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * CTL inserts a control-char at the current location. A message that this
 | |
|  * function is called is displayed at the status line.
 | |
|  */
 | |
| void CTL()
 | |
| {
 | |
|   register char ctrl;
 | |
| 
 | |
|   status_line("Enter control character.", NIL_PTR);
 | |
|   if ((ctrl = getchar()) >= '\01' && ctrl <= '\037') {
 | |
|   	S(ctrl);		/* Insert the char */
 | |
| 	clear_status();
 | |
|   }
 | |
|   else
 | |
| 	error ("Unknown control character", NIL_PTR);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * LIB insert a line at the current position and moves back to the end of
 | |
|  * the previous line.
 | |
|  */
 | |
| void LIB()
 | |
| {
 | |
|   S('\n');	  		/* Insert the line */
 | |
|   UP();				/* Move one line up */
 | |
|   move_to(LINE_END, y);		/* Move to end of this line */
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Line_insert() inserts a new line with text pointed to by `string'.
 | |
|  * It returns the address of the new line.
 | |
|  */
 | |
| LINE *line_insert(line, string, len)
 | |
| register LINE *line;
 | |
| char *string;
 | |
| int len;
 | |
| {
 | |
|   register LINE *new_line;
 | |
| 
 | |
| /* Allocate space for LINE structure and text */
 | |
|   new_line = install_line(string, len);
 | |
| 
 | |
| /* Install the line into the double linked list */
 | |
|   new_line->prev = line;
 | |
|   new_line->next = line->next;
 | |
|   line->next = new_line;
 | |
|   new_line->next->prev = new_line;
 | |
| 
 | |
| /* Increment nlines */
 | |
|   nlines++;
 | |
| 
 | |
|   return new_line;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Insert() insert the string `string' at the given line and location.
 | |
|  */
 | |
| int insert(line, location, string)
 | |
| register LINE *line;
 | |
| char *location, *string;
 | |
| {
 | |
|   register char *bufp = text_buffer;	/* Buffer for building line */
 | |
|   register char *textp = line->text;
 | |
| 
 | |
|   if (length_of(textp) + length_of(string) >= MAX_CHARS) {
 | |
|   	error("Line too long", NIL_PTR);
 | |
|   	return ERRORS;
 | |
|   }
 | |
| 
 | |
|   modified = TRUE;			/* File has been modified */
 | |
| 
 | |
| /* Copy part of line until `location' has been reached */
 | |
|   while (textp != location)
 | |
|   	*bufp++ = *textp++;
 | |
|   
 | |
| /* Insert string at this location */
 | |
|   while (*string != '\0')
 | |
|   	*bufp++ = *string++;
 | |
|   *bufp = '\0';
 | |
|   
 | |
|   if (*(string - 1) == '\n')		/* Insert a new line */
 | |
|   	(void) line_insert(line, location, length_of(location));
 | |
|   else					/* Append last part of line */
 | |
|   	copy_string(bufp, location);
 | |
| 
 | |
| /* Install the new text in this line */
 | |
|   free_space(line->text);
 | |
|   line->text = alloc(length_of(text_buffer) + 1);
 | |
|   copy_string(line->text, text_buffer);
 | |
| 
 | |
|   return FINE;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Line_delete() deletes the argument line out of the line list. The pointer to
 | |
|  * the next line is returned.
 | |
|  */
 | |
| LINE *line_delete(line)
 | |
| register LINE *line;
 | |
| {
 | |
|   register LINE *next_line = line->next;
 | |
| 
 | |
| /* Delete the line */
 | |
|   line->prev->next = line->next;
 | |
|   line->next->prev = line->prev;
 | |
| 
 | |
| /* Free allocated space */
 | |
|   free_space(line->text);
 | |
|   free_space((char*)line);
 | |
| 
 | |
| /* Decrement nlines */
 | |
|   nlines--;
 | |
| 
 | |
|   return next_line;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Delete() deletes all the characters (including newlines) between the
 | |
|  * startposition and endposition and fixes the screen accordingly. It
 | |
|  * returns the number of lines deleted.
 | |
|  */
 | |
| void delete(start_line, start_textp, end_line, end_textp)
 | |
| register LINE *start_line;
 | |
| LINE *end_line;
 | |
| char *start_textp, *end_textp;
 | |
| {
 | |
|   register char *textp = start_line->text;
 | |
|   register char *bufp = text_buffer;	/* Storage for new line->text */
 | |
|   LINE *line, *stop;
 | |
|   int line_cnt = 0;			/* Nr of lines deleted */
 | |
|   int count = 0;
 | |
|   int shift = 0;				/* Used in shift calculation */
 | |
|   int nx = x;
 | |
| 
 | |
|   modified = TRUE;			/* File has been modified */
 | |
| 
 | |
| /* Set up new line. Copy first part of start line until start_position. */
 | |
|   while (textp < start_textp) {
 | |
|   	*bufp++ = *textp++;
 | |
|   	count++;
 | |
|   }
 | |
| 
 | |
| /* Check if line doesn't exceed MAX_CHARS */
 | |
|   if (count + length_of(end_textp) >= MAX_CHARS) {
 | |
|   	error("Line too long", NIL_PTR);
 | |
|   	return;
 | |
|   }
 | |
| 
 | |
| /* Copy last part of end_line if end_line is not tail */
 | |
|   copy_string(bufp, (end_textp != NIL_PTR) ? end_textp : "\n");
 | |
| 
 | |
| /* Delete all lines between start and end_position (including end_line) */
 | |
|   line = start_line->next;
 | |
|   stop = end_line->next;
 | |
|   while (line != stop && line != tail) {
 | |
|   	line = line_delete(line);
 | |
|   	line_cnt++;
 | |
|   }
 | |
| 
 | |
| /* Check if last line of file should be deleted */
 | |
|   if (end_textp == NIL_PTR && length_of(start_line->text) == 1 && nlines > 1) {
 | |
|   	start_line = start_line->prev;
 | |
|   	(void) line_delete(start_line->next);
 | |
|   	line_cnt++;
 | |
|   }
 | |
|   else {	/* Install new text */
 | |
|   	free_space(start_line->text);
 | |
|   	start_line->text = alloc(length_of(text_buffer) + 1);
 | |
|   	copy_string(start_line->text, text_buffer);
 | |
|   }
 | |
| 
 | |
| /* Fix screen. First check if line is shifted. Perhaps we should shift it back*/
 | |
|   if (get_shift(start_line->shift_count)) {
 | |
|   	shift = (XBREAK - count_chars(start_line)) / SHIFT_SIZE;
 | |
|   	if (shift > 0) {		/* Shift line `shift' back */
 | |
|   		if (shift >= get_shift(start_line->shift_count))
 | |
|   			start_line->shift_count = 0;
 | |
|   		else
 | |
|   			start_line->shift_count -= shift;
 | |
|   		nx += shift * SHIFT_SIZE;/* Reset x value */
 | |
|   	}
 | |
|   }
 | |
| 
 | |
|   if (line_cnt == 0) {		    /* Check if only one line changed */
 | |
|   	if (shift > 0) {	    /* Reprint whole line */
 | |
|   		set_cursor(0, y);
 | |
|   		line_print(start_line);
 | |
|   	}
 | |
|   	else {			    /* Just display last part of line */
 | |
|   		set_cursor(x, y);
 | |
|   		put_line(start_line, x, TRUE);
 | |
|   	}
 | |
|   	move_to(nx, y);	   /* Reset cur_text */
 | |
|   	return;
 | |
|   }
 | |
| 
 | |
|   shift = last_y;	   /* Save value */
 | |
|   reset(top_line, y);
 | |
|   display(0, y, start_line, shift - y);
 | |
|   move_to((line_cnt == 1) ? nx : 0, y);
 | |
| }
 | |
| 
 | |
| /*  ========================================================================  *
 | |
|  *				Yank Commands				      *	
 | |
|  *  ========================================================================  */
 | |
| 
 | |
| LINE *mark_line;			/* For marking position. */
 | |
| char *mark_text;
 | |
| int lines_saved;			/* Nr of lines in buffer */
 | |
| 
 | |
| /*
 | |
|  * PT() inserts the buffer at the current location.
 | |
|  */
 | |
| void PT()
 | |
| {
 | |
|   register int fd;		/* File descriptor for buffer */
 | |
| 
 | |
|   if ((fd = scratch_file(READ)) == ERRORS)
 | |
|   	error("Buffer is empty.", NIL_PTR);
 | |
|   else {
 | |
|   	file_insert(fd, FALSE);/* Insert the buffer */
 | |
|   	(void) close(fd);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * IF() prompt for a filename and inserts the file at the current location 
 | |
|  * in the file.
 | |
|  */
 | |
| void IF()
 | |
| {
 | |
|   register int fd;		/* File descriptor of file */
 | |
|   char name[LINE_LEN];		/* Buffer for file name */
 | |
| 
 | |
| /* Get the file name */
 | |
|   if (get_file("Get and insert file:", name) != FINE)
 | |
|   	return;
 | |
|   
 | |
|   if ((fd = open(name, 0)) < 0)
 | |
|   	error("Cannot open ", name);
 | |
|   else {
 | |
|   	file_insert(fd, TRUE);	/* Insert the file */
 | |
|   	(void) close(fd);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * File_insert() inserts a an opened file (as given by filedescriptor fd)
 | |
|  * at the current location.
 | |
|  */
 | |
| void file_insert(fd, old_pos)
 | |
| int fd;
 | |
| FLAG old_pos;
 | |
| {
 | |
|   char line_buffer[MAX_CHARS];		/* Buffer for next line */
 | |
|   register LINE *line = cur_line;
 | |
|   register int line_count = nlines;	/* Nr of lines inserted */
 | |
|   LINE *page = cur_line;
 | |
|   int ret = ERRORS;
 | |
|   
 | |
| /* Get the first piece of text (might be ended with a '\n') from fd */
 | |
|   if (get_line(fd, line_buffer) == ERRORS)
 | |
|   	return;				/* Empty file */
 | |
| 
 | |
| /* Insert this text at the current location. */
 | |
|   if (insert(line, cur_text, line_buffer) == ERRORS)
 | |
|   	return;
 | |
| 
 | |
| /* Repeat getting lines (and inserting lines) until EOF is reached */
 | |
|   while ((ret = get_line(fd, line_buffer)) != ERRORS && ret != NO_LINE)
 | |
|   	line = line_insert(line, line_buffer, ret);
 | |
|   
 | |
|   if (ret == NO_LINE) {		/* Last line read not ended by a '\n' */
 | |
|   	line = line->next;
 | |
|   	(void) insert(line, line->text, line_buffer);
 | |
|   }
 | |
| 
 | |
| /* Calculate nr of lines added */
 | |
|   line_count = nlines - line_count;
 | |
| 
 | |
| /* Fix the screen */
 | |
|   if (line_count == 0) {		/* Only one line changed */
 | |
|   	set_cursor(0, y);
 | |
|   	line_print(line);
 | |
|   	move_to((old_pos == TRUE) ? x : x + length_of(line_buffer), y);
 | |
|   }
 | |
|   else {				/* Several lines changed */
 | |
|   	reset(top_line, y);	/* Reset pointers */
 | |
|   	while (page != line && page != bot_line->next)
 | |
|   		page = page->next;
 | |
|   	if (page != bot_line->next || old_pos == TRUE)
 | |
|   		display(0, y, cur_line, screenmax - y);
 | |
|   	if (old_pos == TRUE)
 | |
|   		move_to(x, y);
 | |
|   	else if (ret == NO_LINE)
 | |
| 		move_to(length_of(line_buffer), find_y(line));
 | |
| 	else 
 | |
| 		move_to(0, find_y(line->next));
 | |
|   }
 | |
| 
 | |
| /* If nr of added line >= REPORT, print the count */
 | |
|   if (line_count >= REPORT)
 | |
|   	status_line(num_out((long) line_count), " lines added.");
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * WB() writes the buffer (yank_file) into another file, which
 | |
|  * is prompted for.
 | |
|  */
 | |
| void WB()
 | |
| {
 | |
|   register int new_fd;		/* Filedescriptor to copy file */
 | |
|   int yank_fd;			/* Filedescriptor to buffer */
 | |
|   register int cnt;		/* Count check for read/write */
 | |
|   int ret = 0;			/* Error check for write */
 | |
|   char file[LINE_LEN];		/* Output file */
 | |
|   
 | |
| /* Checkout the buffer */
 | |
|   if ((yank_fd = scratch_file(READ)) == ERRORS) {
 | |
|   	error("Buffer is empty.", NIL_PTR);
 | |
|   	return;
 | |
|   }
 | |
| 
 | |
| /* Get file name */
 | |
|   if (get_file("Write buffer to file:", file) != FINE)
 | |
|   	return;
 | |
|   
 | |
| /* Creat the new file */
 | |
|   if ((new_fd = creat(file, 0644)) < 0) {
 | |
|   	error("Cannot create ", file);
 | |
|   	return;
 | |
|   }
 | |
| 
 | |
|   status_line("Writing ", file);
 | |
|   
 | |
| /* Copy buffer into file */
 | |
|   while ((cnt = read(yank_fd, text_buffer, sizeof(text_buffer))) > 0)
 | |
|   	if (write(new_fd, text_buffer, cnt) != cnt) {
 | |
|   		bad_write(new_fd);
 | |
|   		ret = ERRORS;
 | |
|   		break;
 | |
|   	}
 | |
| 
 | |
| /* Clean up open files and status_line */
 | |
|   (void) close(new_fd);
 | |
|   (void) close(yank_fd);
 | |
| 
 | |
|   if (ret != ERRORS)			/* Bad write */
 | |
|   	file_status("Wrote", chars_saved, file, lines_saved, TRUE, FALSE);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * MA sets mark_line (mark_text) to the current line (text pointer). 
 | |
|  */
 | |
| void MA()
 | |
| {
 | |
|   mark_line = cur_line;
 | |
|   mark_text = cur_text;
 | |
|   status_line("Mark set", NIL_PTR);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * YA() puts the text between the marked position and the current
 | |
|  * in the buffer.
 | |
|  */
 | |
| void YA()
 | |
| {
 | |
|   set_up(NO_DELETE);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * DT() is essentially the same as YA(), but in DT() the text is deleted.
 | |
|  */
 | |
| void DT()
 | |
| {
 | |
|   set_up(DELETE);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Set_up is an interface to the actual yank. It calls checkmark () to check
 | |
|  * if the marked position is still valid. If it is, yank is called with the
 | |
|  * arguments in the right order.
 | |
|  */
 | |
| void set_up(remove)
 | |
| FLAG remove;				/* DELETE if text should be deleted */
 | |
| {
 | |
|   switch (checkmark()) {
 | |
|   	case NOT_VALID :
 | |
|   		error("Mark not set.", NIL_PTR);
 | |
|   		return;
 | |
|   	case SMALLER :
 | |
|   		yank(mark_line, mark_text, cur_line, cur_text, remove);
 | |
|   		break;
 | |
|   	case BIGGER :
 | |
|   		yank(cur_line, cur_text, mark_line, mark_text, remove);
 | |
|   		break;
 | |
|   	case SAME :		/* Ignore stupid behaviour */
 | |
|   		yank_status = EMPTY;
 | |
|   		chars_saved = 0L;
 | |
|   		status_line("0 characters saved in buffer.", NIL_PTR);
 | |
|   		break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Check_mark() checks if mark_line and mark_text are still valid pointers. If
 | |
|  * they are it returns SMALLER if the marked position is before the current,
 | |
|  * BIGGER if it isn't or SAME if somebody didn't get the point.
 | |
|  * NOT_VALID is returned when mark_line and/or mark_text are no longer valid.
 | |
|  * Legal() checks if mark_text is valid on the mark_line.
 | |
|  */
 | |
| FLAG checkmark()
 | |
| {
 | |
|   register LINE *line;
 | |
|   FLAG cur_seen = FALSE;
 | |
| 
 | |
| /* Special case: check is mark_line and cur_line are the same. */
 | |
|   if (mark_line == cur_line) {
 | |
|   	if (mark_text == cur_text)	/* Even same place */
 | |
|   		return SAME;
 | |
|   	if (legal() == ERRORS)		/* mark_text out of range */
 | |
|   		return NOT_VALID;
 | |
|   	return (mark_text < cur_text) ? SMALLER : BIGGER;
 | |
|   }
 | |
| 
 | |
| /* Start looking for mark_line in the line structure */
 | |
|   for (line = header->next; line != tail; line = line->next) {
 | |
|   	if (line == cur_line)
 | |
|   		cur_seen = TRUE;
 | |
|   	else if (line == mark_line)
 | |
|   		break;
 | |
|   }
 | |
| 
 | |
| /* If we found mark_line (line != tail) check for legality of mark_text */
 | |
|   if (line == tail || legal() == ERRORS)
 | |
|   	return NOT_VALID;
 | |
| 
 | |
| /* cur_seen is TRUE if cur_line is before mark_line */
 | |
|   return (cur_seen == TRUE) ? BIGGER : SMALLER;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Legal() checks if mark_text is still a valid pointer.
 | |
|  */
 | |
| int legal()
 | |
| {
 | |
|   register char *textp = mark_line->text;
 | |
| 
 | |
| /* Locate mark_text on mark_line */
 | |
|   while (textp != mark_text && *textp++ != '\0')
 | |
|   	;
 | |
|   return (*textp == '\0') ? ERRORS : FINE;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Yank puts all the text between start_position and end_position into
 | |
|  * the buffer.
 | |
|  * The caller must check that the arguments to yank() are valid. (E.g. in
 | |
|  * the right order)
 | |
|  */
 | |
| void yank(start_line, start_textp, end_line, end_textp, remove)
 | |
| LINE *start_line, *end_line;
 | |
| char *start_textp, *end_textp;
 | |
| FLAG remove;				/* DELETE if text should be deleted */
 | |
| {
 | |
|   register LINE *line = start_line;
 | |
|   register char *textp = start_textp;
 | |
|   int fd;
 | |
| 
 | |
| /* Creat file to hold buffer */
 | |
|   if ((fd = scratch_file(WRITE)) == ERRORS)
 | |
|   	return;
 | |
|   
 | |
|   chars_saved = 0L;
 | |
|   lines_saved = 0;
 | |
|   status_line("Saving text.", NIL_PTR);
 | |
| 
 | |
| /* Keep writing chars until the end_location is reached. */
 | |
|   while (textp != end_textp) {
 | |
|   	if (write_char(fd, *textp) == ERRORS) {
 | |
|   		(void) close(fd);
 | |
|   		return;
 | |
|   	}
 | |
|   	if (*textp++ == '\n') {	/* Move to the next line */
 | |
|   		line = line->next;
 | |
|   		textp = line->text;
 | |
|   		lines_saved++;
 | |
|   	}
 | |
|   	chars_saved++;
 | |
|   }
 | |
| 
 | |
| /* Flush the I/O buffer and close file */
 | |
|   if (flush_buffer(fd) == ERRORS) {
 | |
|   	(void) close(fd);
 | |
|   	return;
 | |
|   }
 | |
|   (void) close(fd);
 | |
|   yank_status = VALID;
 | |
| 
 | |
| /*
 | |
|  * Check if the text should be deleted as well. If it should, the following
 | |
|  * hack is used to save a lot of code. First move back to the start_position.
 | |
|  * (This might be the location we're on now!) and them delete the text.
 | |
|  * It might be a bit confusing the first time somebody uses it.
 | |
|  * Delete() will fix the screen.
 | |
|  */
 | |
|   if (remove == DELETE) {
 | |
|   	move_to(find_x(start_line, start_textp), find_y(start_line));
 | |
|   	delete(start_line, start_textp, end_line, end_textp);
 | |
|   }
 | |
| 
 | |
|   status_line(num_out(chars_saved), " characters saved in buffer.");
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Scratch_file() creates a uniq file in /usr/tmp. If the file couldn't
 | |
|  * be created other combinations of files are tried until a maximum
 | |
|  * of MAXTRAILS times. After MAXTRAILS times, an error message is given
 | |
|  * and ERRORS is returned.
 | |
|  */
 | |
| 
 | |
| #define MAXTRAILS	26
 | |
| 
 | |
| int scratch_file(mode)
 | |
| FLAG mode;				/* Can be READ or WRITE permission */
 | |
| {
 | |
|   static int trials = 0;		/* Keep track of trails */
 | |
|   register char *y_ptr, *n_ptr;
 | |
|   int fd;				/* Filedescriptor to buffer */
 | |
| 
 | |
| /* If yank_status == NOT_VALID, scratch_file is called for the first time */
 | |
|   if (yank_status == NOT_VALID && mode == WRITE) { /* Create new file */
 | |
|   	/* Generate file name. */
 | |
| 	y_ptr = &yank_file[11];
 | |
| 	n_ptr = num_out((long) getpid());
 | |
| 	while ((*y_ptr = *n_ptr++) != '\0')
 | |
| 		y_ptr++;
 | |
| 	*y_ptr++ = 'a' + trials;
 | |
| 	*y_ptr = '\0';
 | |
|   	/* Check file existence */
 | |
|   	if (access(yank_file, 0) == 0 || (fd = creat(yank_file, 0644)) < 0) {
 | |
|   		if (trials++ >= MAXTRAILS) {
 | |
|   			error("Unable to creat scratchfile.", NIL_PTR);
 | |
|   			return ERRORS;
 | |
|   		}
 | |
|   		else
 | |
|   			return scratch_file(mode);/* Have another go */
 | |
|   	}
 | |
|   }
 | |
|   else if ((mode == READ && (fd = open(yank_file, 0)) < 0) ||
 | |
| 			(mode == WRITE && (fd = creat(yank_file, 0644)) < 0)) {
 | |
|   	yank_status = NOT_VALID;
 | |
|   	return ERRORS;
 | |
|   }
 | |
| 
 | |
|   clear_buffer();
 | |
|   return fd;
 | |
| }
 | |
| 
 | |
| /*  ========================================================================  *
 | |
|  *				Search Routines				      *	
 | |
|  *  ========================================================================  */
 | |
| 
 | |
| /*
 | |
|  * A regular expression consists of a sequence of:
 | |
|  * 	1. A normal character matching that character.
 | |
|  * 	2. A . matching any character.
 | |
|  * 	3. A ^ matching the begin of a line.
 | |
|  * 	4. A $ (as last character of the pattern) mathing the end of a line.
 | |
|  * 	5. A \<character> matching <character>.
 | |
|  * 	6. A number of characters enclosed in [] pairs matching any of these
 | |
|  * 	   characters. A list of characters can be indicated by a '-'. So
 | |
|  * 	   [a-z] matches any letter of the alphabet. If the first character
 | |
|  * 	   after the '[' is a '^' then the set is negated (matching none of
 | |
|  * 	   the characters). 
 | |
|  * 	   A ']', '^' or '-' can be escaped by putting a '\' in front of it.
 | |
|  * 	7. If one of the expressions as described in 1-6 is followed by a
 | |
|  * 	   '*' than that expressions matches a sequence of 0 or more of
 | |
|  * 	   that expression.
 | |
|  */
 | |
| 
 | |
| char typed_expression[LINE_LEN];	/* Holds previous expr. */
 | |
| 
 | |
| /*
 | |
|  * SF searches forward for an expression.
 | |
|  */
 | |
| void SF()
 | |
| {
 | |
|   search("Search forward:", FORWARD);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * SF searches backwards for an expression.
 | |
|  */
 | |
| void SR()
 | |
| {
 | |
|   search("Search reverse:", REVERSE);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Get_expression() prompts for an expression. If just a return is typed, the
 | |
|  * old expression is used. If the expression changed, compile() is called and
 | |
|  * the returning REGEX structure is returned. It returns NIL_REG upon error.
 | |
|  * The save flag indicates whether the expression should be appended at the
 | |
|  * message pointer.
 | |
|  */
 | |
| REGEX *get_expression(message)
 | |
| char *message;
 | |
| {
 | |
|   static REGEX program;			/* Program of expression */
 | |
|   char exp_buf[LINE_LEN];			/* Buffer for new expr. */
 | |
| 
 | |
|   if (get_string(message, exp_buf, FALSE) == ERRORS)
 | |
|   	return NIL_REG;
 | |
|   
 | |
|   if (exp_buf[0] == '\0' && typed_expression[0] == '\0') {
 | |
|   	error("No previous expression.", NIL_PTR);
 | |
|   	return NIL_REG;
 | |
|   }
 | |
| 
 | |
|   if (exp_buf[0] != '\0') {		/* A new expr. is typed */
 | |
|   	copy_string(typed_expression, exp_buf);/* Save expr. */
 | |
|   	compile(exp_buf, &program);	/* Compile new expression */
 | |
|   }
 | |
| 
 | |
|   if (program.status == REG_ERROR) {	/* Error during compiling */
 | |
|   	error(program.result.err_mess, NIL_PTR);
 | |
|   	return NIL_REG;
 | |
|   }
 | |
|   return &program;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * GR() a replaces all matches from the current position until the end
 | |
|  * of the file.
 | |
|  */
 | |
| void GR()
 | |
| {
 | |
|   change("Global replace:", VALID);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * LR() replaces all matches on the current line.
 | |
|  */
 | |
| void LR()
 | |
| {
 | |
|   change("Line replace:", NOT_VALID);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Change() prompts for an expression and a substitution pattern and changes
 | |
|  * all matches of the expression into the substitution. change() start looking
 | |
|  * for expressions at the current line and continues until the end of the file
 | |
|  * if the FLAG file is VALID.
 | |
|  */
 | |
| void change(message, file)
 | |
| char *message;				/* Message to prompt for expression */
 | |
| FLAG file;
 | |
| {
 | |
|   char mess_buf[LINE_LEN];	/* Buffer to hold message */
 | |
|   char replacement[LINE_LEN];	/* Buffer to hold subst. pattern */
 | |
|   REGEX *program;			/* Program resulting from compilation */
 | |
|   register LINE *line = cur_line;
 | |
|   register char *textp;
 | |
|   long lines = 0L;		/* Nr of lines on which subs occurred */
 | |
|   long subs = 0L;			/* Nr of subs made */
 | |
|   int page = y;			/* Index to check if line is on screen*/
 | |
| 
 | |
| /* Save message and get expression */
 | |
|   copy_string(mess_buf, message);
 | |
|   if ((program = get_expression(mess_buf)) == NIL_REG)
 | |
|   	return;
 | |
|   
 | |
| /* Get substitution pattern */
 | |
|   build_string(mess_buf, "%s %s by:", mess_buf, typed_expression);
 | |
|   if (get_string(mess_buf, replacement, FALSE) == ERRORS)
 | |
|   	return;
 | |
|   
 | |
|   set_cursor(0, ymax);
 | |
|   flush();
 | |
| /* Substitute until end of file */
 | |
|   do {
 | |
|   	if (line_check(program, line->text, FORWARD)) {
 | |
|   		lines++;
 | |
|   		/* Repeat sub. on this line as long as we find a match*/
 | |
|   		do {
 | |
|   			subs++;	/* Increment subs */
 | |
|   			if ((textp = substitute(line, program,replacement))
 | |
| 								     == NIL_PTR)
 | |
|   				return;	/* Line too long */
 | |
|   		} while ((program->status & BEGIN_LINE) != BEGIN_LINE &&
 | |
| 			 (program->status & END_LINE) != END_LINE &&
 | |
| 					  line_check(program, textp, FORWARD));
 | |
|   		/* Check to see if we can print the result */
 | |
|   		if (page <= screenmax) {
 | |
|   			set_cursor(0, page);
 | |
|   			line_print(line);
 | |
|   		}
 | |
|   	}
 | |
|   	if (page <= screenmax)
 | |
|   		page++;
 | |
|   	line = line->next;
 | |
|   } while (line != tail && file == VALID && quit == FALSE);
 | |
| 
 | |
|   copy_string(mess_buf, (quit == TRUE) ? "(Aborted) " : "");
 | |
| /* Fix the status line */
 | |
|   if (subs == 0L && quit == FALSE)
 | |
|   	error("Pattern not found.", NIL_PTR);
 | |
|   else if (lines >= REPORT || quit == TRUE) {
 | |
|   	build_string(mess_buf, "%s %D substitutions on %D lines.", mess_buf,
 | |
| 								   subs, lines);
 | |
|   	status_line(mess_buf, NIL_PTR);
 | |
|   }
 | |
|   else if (file == NOT_VALID && subs >= REPORT)
 | |
|   	status_line(num_out(subs), " substitutions.");
 | |
|   else
 | |
|   	clear_status();
 | |
|   move_to (x, y);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Substitute() replaces the match on this line by the substitute pattern
 | |
|  * as indicated by the program. Every '&' in the replacement is replaced by 
 | |
|  * the original match. A \ in the replacement escapes the next character.
 | |
|  */
 | |
| char *substitute(line, program, replacement)
 | |
| LINE *line;
 | |
| REGEX *program;
 | |
| char *replacement;		/* Contains replacement pattern */
 | |
| {
 | |
|   register char *textp = text_buffer;
 | |
|   register char *subp = replacement;
 | |
|   char *linep = line->text;
 | |
|   char *amp;
 | |
| 
 | |
|   modified = TRUE;
 | |
| 
 | |
| /* Copy part of line until the beginning of the match */
 | |
|   while (linep != program->start_ptr)
 | |
|   	*textp++ = *linep++;
 | |
|   
 | |
| /*
 | |
|  * Replace the match by the substitution pattern. Each occurrence of '&' is
 | |
|  * replaced by the original match. A \ escapes the next character.
 | |
|  */
 | |
|   while (*subp != '\0' && textp < &text_buffer[MAX_CHARS]) {
 | |
|   	if (*subp == '&') {		/* Replace the original match */
 | |
|   		amp = program->start_ptr;
 | |
|   		while (amp < program->end_ptr && textp<&text_buffer[MAX_CHARS])
 | |
|   			*textp++ = *amp++;
 | |
|   		subp++;
 | |
|   	}
 | |
|   	else {
 | |
|   		if (*subp == '\\' && *(subp + 1) != '\0')
 | |
|   			subp++;
 | |
|   		*textp++ = *subp++;
 | |
|   	}
 | |
|   }
 | |
| 
 | |
| /* Check for line length not exceeding MAX_CHARS */
 | |
|   if (length_of(text_buffer) + length_of(program->end_ptr) >= MAX_CHARS) {
 | |
|   	error("Substitution result: line too big", NIL_PTR);
 | |
|   	return NIL_PTR;
 | |
|   }
 | |
| 
 | |
| /* Append last part of line to the new build line */
 | |
|   copy_string(textp, program->end_ptr);
 | |
| 
 | |
| /* Free old line and install new one */
 | |
|   free_space(line->text);
 | |
|   line->text = alloc(length_of(text_buffer) + 1);
 | |
|   copy_string(line->text, text_buffer);
 | |
| 
 | |
|   return(line->text + (textp - text_buffer));
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Search() calls get_expression to fetch the expression. If this went well,
 | |
|  * the function match() is called which returns the line with the next match.
 | |
|  * If this line is the NIL_LINE, it means that a match could not be found.
 | |
|  * Find_x() and find_y() display the right page on the screen, and return
 | |
|  * the right coordinates for x and y. These coordinates are passed to move_to()
 | |
|  */
 | |
| void search(message, method)
 | |
| char *message;
 | |
| FLAG method;
 | |
| {
 | |
|   register REGEX *program;
 | |
|   register LINE *match_line;
 | |
| 
 | |
| /* Get the expression */
 | |
|   if ((program = get_expression(message)) == NIL_REG)
 | |
|   	return;
 | |
| 
 | |
|   set_cursor(0, ymax);
 | |
|   flush();
 | |
| /* Find the match */
 | |
|   if ((match_line = match(program, cur_text, method)) == NIL_LINE) {
 | |
|   	if (quit == TRUE)
 | |
|   		status_line("Aborted", NIL_PTR);
 | |
|   	else
 | |
|   		status_line("Pattern not found.", NIL_PTR);
 | |
|   	return;
 | |
|   }
 | |
| 
 | |
|   move(0, program->start_ptr, find_y(match_line));
 | |
|   clear_status();
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * find_y() checks if the matched line is on the current page.  If it is, it
 | |
|  * returns the new y coordinate, else it displays the correct page with the
 | |
|  * matched line in the middle and returns the new y value;
 | |
|  */
 | |
| int find_y(match_line)
 | |
| LINE *match_line;
 | |
| {
 | |
|   register LINE *line;
 | |
|   register int count = 0;
 | |
| 
 | |
| /* Check if match_line is on the same page as currently displayed. */
 | |
|   for (line = top_line; line != match_line && line != bot_line->next;
 | |
|   						      line = line->next)
 | |
|   	count++;
 | |
|   if (line != bot_line->next)
 | |
|   	return count;
 | |
| 
 | |
| /* Display new page, with match_line in center. */
 | |
|   if ((line = proceed(match_line, -(screenmax >> 1))) == header) {
 | |
|   /* Can't display in the middle. Make first line of file top_line */
 | |
|   	count = 0;
 | |
|   	for (line = header->next; line != match_line; line = line->next)
 | |
|   		count++;
 | |
|   	line = header->next;
 | |
|   }
 | |
|   else	/* New page is displayed. Set cursor to middle of page */
 | |
|   	count = screenmax >> 1;
 | |
| 
 | |
| /* Reset pointers and redraw the screen */
 | |
|   reset(line, 0);
 | |
|   RD();
 | |
| 
 | |
|   return count;
 | |
| }
 | |
| 
 | |
| /* Opcodes for characters */
 | |
| #define	NORMAL		0x0200
 | |
| #define DOT		0x0400
 | |
| #define EOLN		0x0800
 | |
| #define STAR		0x1000
 | |
| #define BRACKET		0x2000
 | |
| #define NEGATE		0x0100
 | |
| #define DONE		0x4000
 | |
| 
 | |
| /* Mask for opcodes and characters */
 | |
| #define LOW_BYTE	0x00FF
 | |
| #define HIGH_BYTE	0xFF00
 | |
| 
 | |
| /* Previous is the contents of the previous address (ptr) points to */
 | |
| #define previous(ptr)		(*((ptr) - 1))
 | |
| 
 | |
| /* Buffer to store outcome of compilation */
 | |
| int exp_buffer[BLOCK_SIZE];
 | |
| 
 | |
| /* Errors often used */
 | |
| char *too_long = "Regular expression too long";
 | |
| 
 | |
| /*
 | |
|  * Reg_error() is called by compile() is something went wrong. It set the
 | |
|  * status of the structure to error, and assigns the error field of the union.
 | |
|  */
 | |
| #define reg_error(str)	program->status = REG_ERROR, \
 | |
|   					program->result.err_mess = (str)
 | |
| /*
 | |
|  * Finished() is called when everything went right during compilation. It
 | |
|  * allocates space for the expression, and copies the expression buffer into
 | |
|  * this field.
 | |
|  */
 | |
| void finished(program, last_exp)
 | |
| register REGEX *program;
 | |
| int *last_exp;
 | |
| {
 | |
|   register int length = (last_exp - exp_buffer) * sizeof(int);
 | |
| 
 | |
| /* Allocate space */
 | |
|   program->result.expression = (int *) alloc(length);
 | |
| /* Copy expression. (expression consists of ints!) */
 | |
|   bcopy(exp_buffer, program->result.expression, length);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Compile compiles the pattern into a more comprehensible form and returns a 
 | |
|  * REGEX structure. If something went wrong, the status field of the structure
 | |
|  * is set to REG_ERROR and an error message is set into the err_mess field of
 | |
|  * the union. If all went well the expression is saved and the expression
 | |
|  * pointer is set to the saved (and compiled) expression.
 | |
|  */
 | |
| void compile(pattern, program)
 | |
| register char *pattern;			/* Pointer to pattern */
 | |
| REGEX *program;
 | |
| {
 | |
|   register int *expression = exp_buffer;
 | |
|   int *prev_char;			/* Pointer to previous compiled atom */
 | |
|   int *acct_field;		/* Pointer to last BRACKET start */
 | |
|   FLAG negate;			/* Negate flag for BRACKET */
 | |
|   char low_char;			/* Index for chars in BRACKET */
 | |
|   char c;
 | |
| 
 | |
| /* Check for begin of line */
 | |
|   if (*pattern == '^') {
 | |
|   	program->status = BEGIN_LINE;
 | |
|   	pattern++;
 | |
|   }
 | |
|   else {
 | |
|   	program->status = 0;
 | |
| /* If the first character is a '*' we have to assign it here. */
 | |
|   	if (*pattern == '*') {
 | |
|   		*expression++ = '*' + NORMAL;
 | |
|   		pattern++;
 | |
|   	}
 | |
|   }
 | |
| 
 | |
|   for (; ;) {
 | |
|   	switch (c = *pattern++) {
 | |
|   	case '.' :
 | |
|   		*expression++ = DOT;
 | |
|   		break;
 | |
|   	case '$' :
 | |
|   		/*
 | |
|   		 * Only means EOLN if it is the last char of the pattern
 | |
|   		 */
 | |
|   		if (*pattern == '\0') {
 | |
|   			*expression++ = EOLN | DONE;
 | |
|   			program->status |= END_LINE;
 | |
|   			finished(program, expression);
 | |
|   			return;
 | |
|   		}
 | |
|   		else
 | |
|   			*expression++ = NORMAL + '$';
 | |
|   		break;
 | |
|   	case '\0' :
 | |
|   		*expression++ = DONE;
 | |
|   		finished(program, expression);
 | |
|   		return;
 | |
|   	case '\\' :
 | |
|   		/* If last char, it must! mean a normal '\' */
 | |
|   		if (*pattern == '\0')
 | |
|   			*expression++ = NORMAL + '\\';
 | |
|   		else
 | |
|   			*expression++ = NORMAL + *pattern++;
 | |
|   		break;
 | |
|   	case '*' :
 | |
|   		/*
 | |
|   		 * If the previous expression was a [] find out the
 | |
|   		 * begin of the list, and adjust the opcode.
 | |
|   		 */
 | |
|   		prev_char = expression - 1;
 | |
|   		if (*prev_char & BRACKET)
 | |
|   			*(expression - (*acct_field & LOW_BYTE))|= STAR;
 | |
|   		else
 | |
|   			*prev_char |= STAR;
 | |
|   		break;
 | |
|   	case '[' :
 | |
|   		/*
 | |
|   		 * First field in expression gives information about
 | |
|   		 * the list.
 | |
|   		 * The opcode consists of BRACKET and if necessary
 | |
|   		 * NEGATE to indicate that the list should be negated
 | |
|   		 * and/or STAR to indicate a number of sequence of this 
 | |
|   		 * list.
 | |
|   		 * The lower byte contains the length of the list.
 | |
|   		 */
 | |
|   		acct_field = expression++;
 | |
|   		if (*pattern == '^') {	/* List must be negated */
 | |
|   			pattern++;
 | |
|   			negate = TRUE;
 | |
|   		}
 | |
|   		else
 | |
|   			negate = FALSE;
 | |
|   		while (*pattern != ']') {
 | |
|   			if (*pattern == '\0') {
 | |
|   				reg_error("Missing ]");
 | |
|   				return;
 | |
|   			}
 | |
|   			if (*pattern == '\\')
 | |
|   				pattern++;
 | |
|   			*expression++ = *pattern++;
 | |
|   			if (*pattern == '-') {
 | |
|   						/* Make list of chars */
 | |
|   				low_char = previous(pattern);
 | |
|   				pattern++;	/* Skip '-' */
 | |
|   				if (low_char++ > *pattern) {
 | |
|   					reg_error("Bad range in [a-z]");
 | |
|   					return;
 | |
|   				}
 | |
|   				/* Build list */
 | |
|   				while (low_char <= *pattern)
 | |
|   					*expression++ = low_char++;
 | |
|   				pattern++;
 | |
|   			}
 | |
|   			if (expression >= &exp_buffer[BLOCK_SIZE]) {
 | |
|   				reg_error(too_long);
 | |
|   				return;
 | |
|   			}
 | |
|   		}
 | |
|   		pattern++;			/* Skip ']' */
 | |
|   		/* Assign length of list in acct field */
 | |
|   		if ((*acct_field = (expression - acct_field)) == 1) {
 | |
|   			reg_error("Empty []");
 | |
|   			return;
 | |
|   		}
 | |
|   		/* Assign negate and bracket field */
 | |
|   		*acct_field |= BRACKET;
 | |
|   		if (negate == TRUE)
 | |
|   			*acct_field |= NEGATE;
 | |
|   		/*
 | |
|   		 * Add BRACKET to opcode of last char in field because
 | |
|   		 * a '*' may be following the list.
 | |
|   		 */
 | |
|   		previous(expression) |= BRACKET;
 | |
|   		break;
 | |
|   	default :
 | |
|   		*expression++ = c + NORMAL;
 | |
|   	}
 | |
|   	if (expression == &exp_buffer[BLOCK_SIZE]) {
 | |
|   		reg_error(too_long);
 | |
|   		return;
 | |
|   	}
 | |
|   }
 | |
|   /* NOTREACHED */
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Match gets as argument the program, pointer to place in current line to 
 | |
|  * start from and the method to search for (either FORWARD or REVERSE).
 | |
|  * Match() will look through the whole file until a match is found.
 | |
|  * NIL_LINE is returned if no match could be found.
 | |
|  */
 | |
| LINE *match(program, string, method)
 | |
| REGEX *program;
 | |
| char *string;
 | |
| register FLAG method;
 | |
| {
 | |
|   register LINE *line = cur_line;
 | |
|   char old_char;				/* For saving chars */
 | |
| 
 | |
| /* Corrupted program */
 | |
|   if (program->status == REG_ERROR)
 | |
|   	return NIL_LINE;
 | |
| 
 | |
| /* Check part of text first */
 | |
|   if (!(program->status & BEGIN_LINE)) {
 | |
|   	if (method == FORWARD) {
 | |
|   		if (line_check(program, string + 1, method) == MATCH)
 | |
|   			return cur_line;	/* Match found */
 | |
|   	}
 | |
|   	else if (!(program->status & END_LINE)) {
 | |
|   		old_char = *string;	/* Save char and */
 | |
|   		*string = '\n';		/* Assign '\n' for line_check */
 | |
|   		if (line_check(program, line->text, method) == MATCH) {
 | |
|   			*string = old_char; /* Restore char */
 | |
|   			return cur_line;    /* Found match */
 | |
|   		}
 | |
|   		*string = old_char;	/* No match, but restore char */
 | |
|   	}
 | |
|   }
 | |
| 
 | |
| /* No match in last (or first) part of line. Check out rest of file */
 | |
|   do {
 | |
|   	line = (method == FORWARD) ? line->next : line->prev;
 | |
|   	if (line->text == NIL_PTR)	/* Header/tail */
 | |
|   		continue;
 | |
|   	if (line_check(program, line->text, method) == MATCH)
 | |
|   		return line;
 | |
|   } while (line != cur_line && quit == FALSE);
 | |
| 
 | |
| /* No match found. */
 | |
|   return NIL_LINE;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Line_check() checks the line (or rather string) for a match. Method
 | |
|  * indicates FORWARD or REVERSE search. It scans through the whole string
 | |
|  * until a match is found, or the end of the string is reached.
 | |
|  */
 | |
| int line_check(program, string, method)
 | |
| register REGEX *program;
 | |
| char *string;
 | |
| FLAG method;
 | |
| {
 | |
|   register char *textp = string;
 | |
| 
 | |
| /* Assign start_ptr field. We might find a match right away! */
 | |
|   program->start_ptr = textp;
 | |
| 
 | |
| /* If the match must be anchored, just check the string. */
 | |
|   if (program->status & BEGIN_LINE)
 | |
|   	return check_string(program, string, NIL_INT);
 | |
|   
 | |
|   if (method == REVERSE) {
 | |
|   	/* First move to the end of the string */
 | |
|   	for (textp = string; *textp != '\n'; textp++)
 | |
|   		;
 | |
|   	/* Start checking string until the begin of the string is met */
 | |
|   	while (textp >= string) {
 | |
|   		program->start_ptr = textp;
 | |
|   		if (check_string(program, textp--, NIL_INT))
 | |
|   			return MATCH;
 | |
|   	}
 | |
|   }
 | |
|   else {
 | |
|   	/* Move through the string until the end of is found */
 | |
| 	while (quit == FALSE && *textp != '\0') {
 | |
|   		program->start_ptr = textp;
 | |
|   		if (check_string(program, textp, NIL_INT))
 | |
|   			return MATCH;
 | |
| 		if (*textp == '\n')
 | |
| 			break;
 | |
| 		textp++;
 | |
|   	}
 | |
|   }
 | |
| 
 | |
|   return NO_MATCH;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Check() checks of a match can be found in the given string. Whenever a STAR
 | |
|  * is found during matching, then the begin position of the string is marked
 | |
|  * and the maximum number of matches is performed. Then the function star()
 | |
|  * is called which starts to finish the match from this position of the string
 | |
|  * (and expression). Check() return MATCH for a match, NO_MATCH is the string 
 | |
|  * couldn't be matched or REG_ERROR for an illegal opcode in expression.
 | |
|  */
 | |
| int check_string(program, string, expression)
 | |
| REGEX *program;
 | |
| register char *string;
 | |
| int *expression;
 | |
| {
 | |
|   register int opcode;		/* Holds opcode of next expr. atom */
 | |
|   char c;				/* Char that must be matched */
 | |
|   char *mark;			/* For marking position */
 | |
|   int star_fl;			/* A star has been born */
 | |
| 
 | |
|   if (expression == NIL_INT)
 | |
|   	expression = program->result.expression;
 | |
| 
 | |
| /* Loop until end of string or end of expression */
 | |
|   while (quit == FALSE && !(*expression & DONE) &&
 | |
| 					   *string != '\0' && *string != '\n') {
 | |
|   	c = *expression & LOW_BYTE;	  /* Extract match char */
 | |
|   	opcode = *expression & HIGH_BYTE; /* Extract opcode */
 | |
|   	if (star_fl = (opcode & STAR)) {  /* Check star occurrence */
 | |
|   		opcode &= ~STAR;	  /* Strip opcode */
 | |
|   		mark = string;		  /* Mark current position */
 | |
|   	}
 | |
|   	expression++;		/* Increment expr. */
 | |
|   	switch (opcode) {
 | |
|   	case NORMAL :
 | |
|   		if (star_fl)
 | |
|   			while (*string++ == c)	/* Skip all matches */
 | |
|   				;
 | |
|   		else if (*string++ != c)
 | |
|   			return NO_MATCH;
 | |
|   		break;
 | |
|   	case DOT :
 | |
|   		string++;
 | |
|   		if (star_fl)			/* Skip to eoln */
 | |
|   			while (*string != '\0' && *string++ != '\n')
 | |
|   				;
 | |
|   		break;
 | |
|   	case NEGATE | BRACKET:
 | |
|   	case BRACKET :
 | |
|   		if (star_fl)
 | |
|   			while (in_list(expression, *string++, c, opcode)
 | |
| 								       == MATCH)
 | |
|   				;
 | |
|   		else if (in_list(expression, *string++, c, opcode) == NO_MATCH)
 | |
|   			return NO_MATCH;
 | |
|   		expression += c - 1;	/* Add length of list */
 | |
|   		break;
 | |
|   	default :
 | |
|   		panic("Corrupted program in check_string()");
 | |
|   	}
 | |
|   	if (star_fl) 
 | |
|   		return star(program, mark, string, expression);
 | |
|   }
 | |
|   if (*expression & DONE) {
 | |
|   	program->end_ptr = string;	/* Match ends here */
 | |
|   	/*
 | |
|   	 * We might have found a match. The last thing to do is check
 | |
|   	 * whether a '$' was given at the end of the expression, or
 | |
|   	 * the match was found on a null string. (E.g. [a-z]* always
 | |
|   	 * matches) unless a ^ or $ was included in the pattern.
 | |
|   	 */
 | |
|   	if ((*expression & EOLN) && *string != '\n' && *string != '\0')
 | |
|   		return NO_MATCH;
 | |
| 	if (string == program->start_ptr && !(program->status & BEGIN_LINE)
 | |
| 					 && !(*expression & EOLN))
 | |
|   		return NO_MATCH;
 | |
|   	return MATCH;
 | |
|   }
 | |
|   return NO_MATCH;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Star() calls check_string() to find out the longest match possible.
 | |
|  * It searches backwards until the (in check_string()) marked position
 | |
|  * is reached, or a match is found.
 | |
|  */
 | |
| int star(program, end_position, string, expression)
 | |
| REGEX *program;
 | |
| register char *end_position;
 | |
| register char *string;
 | |
| int *expression;
 | |
| {
 | |
|   do {
 | |
|   	string--;
 | |
|   	if (check_string(program, string, expression))
 | |
|   		return MATCH;
 | |
|   } while (string != end_position);
 | |
| 
 | |
|   return NO_MATCH;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * In_list() checks if the given character is in the list of []. If it is
 | |
|  * it returns MATCH. if it isn't it returns NO_MATCH. These returns values
 | |
|  * are reversed when the NEGATE field in the opcode is present.
 | |
|  */
 | |
| int in_list(list, c, list_length, opcode)
 | |
| register int *list;
 | |
| char c;
 | |
| register int list_length;
 | |
| int opcode;
 | |
| {
 | |
|   if (c == '\0' || c == '\n')	/* End of string, never matches */
 | |
|   	return NO_MATCH;
 | |
|   while (list_length-- > 1) {	/* > 1, don't check acct_field */
 | |
|   	if ((*list & LOW_BYTE) == c)
 | |
|   		return (opcode & NEGATE) ? NO_MATCH : MATCH;
 | |
|   	list++;
 | |
|   }
 | |
|   return (opcode & NEGATE) ? MATCH : NO_MATCH;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Dummy_line() adds an empty line at the end of the file. This is sometimes
 | |
|  * useful in combination with the EF and DN command in combination with the
 | |
|  * Yank command set.
 | |
|  */
 | |
| void dummy_line()
 | |
| {
 | |
| 	(void) line_insert(tail->prev, "\n", 1);
 | |
| 	tail->prev->shift_count = DUMMY;
 | |
| 	if (last_y != screenmax) {
 | |
| 		last_y++;
 | |
| 		bot_line = bot_line->next;
 | |
| 	}
 | |
| }
 | 
