From: wolff@inf.fu-berlin.de (Thomas Wolff)
Newsgroups: comp.editors
Subject: Editor mined (3/4)
Date: 28 Jul 92 15:03:50 GMT
Organization: Free University of Berlin, Germany

#! /bin/sh
: This is a sharchive -- extract the files by running through sh

echo ---------------------- extracting mined2.c -----------------
sed 's/^,//' << \EOSED > mined2.c
,/*  ==================================================================	*
, *				Editor mined				*
, *				Part 2					*
, *  ==================================================================	*/
,
,#include "mined.h"
,
,/*  ==================================================================	*
, *			Definitions specific for mined2.c		*
, *  ==================================================================	*/
,
,#ifdef vms
,#define copycommand "copy %s %s"
,#endif
,#ifdef msdos
,#define copycommand "copy %s %s > nul:"
,#endif
,
,/*  ==================================================================	*
, *				Move Commands				*
, *  ==================================================================	*/
,
,/*
, * Move one line up.
, */
,MUP ()
,{
,  if (hop_flag > 0) return HIGH ();
,  if (y == 0) {		/* Top line of screen. Scroll one line */
,	(void) reverse_scroll ();
,	set_cursor (0, YMAX);		/* Erase very bottom line */
,	clear_eol ();
,	move_y (y);
,  }
,  else			/* Move to previous line */
,	move_y (y - 1);
,}
,
,/*
, * Move one line down.
, */
,MDN ()
,{
,  if (hop_flag > 0) return LOW ();
,  if (y == last_y) {	/* Last line of screen. Scroll one line */
,	if (bot_line->next == tail && bot_line->text [0] != '\n') {
,	/*	dummy_line ();	don't create new empty line ! */
,	/*	MDN (); */
,		return;
,	}
,	else {
,		(void) forward_scroll ();
,		move_y (y);
,	}
,  }
,  else			/* Move to next line */
,	move_y (y + 1);
,}
,
,/*
, * Move left one position.
, */
,MLF ()
,{
,  if (hop_flag > 0) return BL ();
,  if (x == 0 && get_shift (cur_line->shift_count) == 0) {/* Begin of line */
,	if (cur_line->prev != header) {
,		MUP ();					/* Move one line up */
,		move_to (LINE_END, y);
,	}
,  }
,  else
,	move_to (x - 1, y);
,}
,
,/*
, * Move right one position.
, */
,MRT ()
,{
,  if (hop_flag > 0) return EL ();
,  if (* cur_text == '\n') {
,	if (cur_line->next != tail) {		/* Last char of file */
,		MDN ();				/* Move one line down */
,		move_to (LINE_START, y);
,	}
,  }
,  else
,	move_to (x + 1, y);
,}
,
,/*
, * Move to top of screen
, */
,HIGH ()
,{
,  move_y (0);
,}
,
,/*
, * Move to bottom of screen
, */
,LOW ()
,{
,  move_y (last_y);
,}
,
,/*
, * Move to begin of line.
, */
,BL ()
,{
,  move_to (LINE_START, y);
,}
,
,/*
, * Move to end of line.
, */
,EL ()
,{
,  move_to (LINE_END, y);
,}
,
,/*
, * GOTO () prompts for a linenumber and moves to that line.
, */
,GOTO ()
,{
,  u_char c;
,  char end;
,  extern int (* key_map []) ();
,  int number;
,
,  if (! char_ready_within (500)) status_msg ("HOP ... command (fortified) or line number...");
,  if (quit == TRUE) return;
,  c = readchar ();
,  if (quit == TRUE) return;
,  if (('0' <= c) && (c <= '9')) {
,	end = get_number ("Please continue line number...", c, & number);
,	if (end == '%')
,		goproz (number);
,	else if (end != ERRORS)
,		goline (number);
,	return;
,  }
,  else {
,	clear_status ();
,	hop_flag = 1;
,	(* key_map [c]) (c);
,	return;
,  }
,}
,
,goline (number)
,  int number;
,{
,  LINE * line;
,  if (number <= 0 || (line = proceed (header->next, number - 1)) == tail)
,	error ("Illegal line number: ", num_out ((long) number));
,  else	{
,	  clear_status ();
,	  move_y (find_y (line));
,	}
,}
,
,goproz (number)
,  int number;
,{
,  goline (number * nlines / 100);
,}
,
,/*
, * 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.
, */
,PD ()
,{
,  register int i;
,
,  if (hop_flag > 0) {hop_flag = 0; return EF ();}
,  for (i = 0; i < SCREENMAX; i ++)
,	if (forward_scroll () == ERRORS)
,		break;			/* EOF reached */
,  if (y - i < 0)			/* Line no longer on screen */
,	move_y (SCREENMAX >> 1);
,  else
,	move_y (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.
, */
,PU ()
,{
,  register int i;
,
,  if (hop_flag > 0) {hop_flag = 0; return HOME ();}
,  for (i = 0; i < SCREENMAX; i ++)
,	if (reverse_scroll () == ERRORS)
,		break;			/* Top of file reached */
,  set_cursor (0, YMAX);			/* Erase very bottom line */
,  clear_eol ();
,  if (y + i > SCREENMAX)		/* line no longer on screen */
,	move_y (SCREENMAX >> 1);
,  else
,	move_y (y + i);
,}
,
,/*
, * Go to top of file, scrolling if possible, else redrawing screen.
, */
,HOME ()
,{
,  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
, */
,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_END /* not START (EF!) */, last_y);
,}
,
,/*
, * Scroll one line up. Leave the cursor on the same line (if possible).
, */
,SU ()
,{
,  register int i;
,
,  if (hop_flag > 0) {
,	hop_flag = 0;
,	for (i = 0; i < SCREENMAX >> 1; i ++) SU ();
,	return;
,  }
,
,  if (top_line->prev == header)	/* Top of file. Can't scroll */
,	return;
,  (void) reverse_scroll ();
,  set_cursor (0, YMAX);		/* Erase very bottom line */
,  clear_eol ();
,  move_y ((y == SCREENMAX) ? SCREENMAX : y + 1);
,}
,
,/*
, * Scroll one line down. Leave the cursor on the same line (if possible).
, */
,SD ()
,{
,  register int i;
,
,  if (hop_flag > 0) {
,	hop_flag = 0;
,	for (i = 0; i < SCREENMAX >> 1; i ++) SD ();
,	return;
,  }
,
,  if (forward_scroll () != ERRORS)
,	move_y ((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.
, */
,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;
,  scroll_forward ();
,  set_cursor (0, SCREENMAX);
,  line_print (bot_line);
,
,  return FINE;
,}
,
,/*
, * Perform a backwards scroll. It returns ERRORS if we're at the first line
, * of the file.
, */
,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);
,  scroll_reverse ();
,  set_cursor (0, 0);
,  line_print (top_line);
,
,  return FINE;
,}
,
,/*----------------------*
, *	Word moves	*
, *----------------------*/
,/*
, * Word definitions
, */
,#define white_space(c)	((c) == ' ' || (c) == '\t')
,#define alpha(c)	((c) != ' ' && (c) != '\t' && (c) != '\n')
,/*
, * A word was previously defined as a number of non-blank characters
, * separated by tabs, spaces or linefeeds.
, * By consulting idfchar (), sequences of real letters only or digits
, * or underlines are recognized as words.
, */
,int
,idfchar (ch)
,  u_char ch;
,{
,  return ('0' <= ch && ch <= '9') ||
,	 ('A' <= ch && ch <= 'Z') ||
,	 ('a' <= ch && ch <= 'z') ||
,#ifdef msdos
,	 ((u_char) '\200' <= ch) ||	/* might be more specific */
,#else
,	 ((u_char) '\300' <= ch && ch != (u_char) 'W' && ch != (u_char) 'w') ||
,	 (ch == (u_char) '5') ||
,#endif
,	 (ch == '_');
,}
,
,/*
, * MPW () 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.
, */
,MPW ()
,{
,  if (hop_flag > 0) return BSEN ();
,  move_previous_word (NO_DELETE);
,}
,
,move_previous_word (remove)
,  FLAG remove;
,{
,  register char * begin_line;
,  register char * textp;
,  char start_char = * cur_text;
,  char * start_pos = cur_text;
,  FLAG idfsearch;
,
,  if ((remove == DELETE) && (viewonly == TRUE))
,	return viewonlyerr ();
,
,/* First check if we're at the beginning of line. */
,  if (cur_text == cur_line->text) {
,	if (cur_line->prev == header)
,		return;
,	start_char = '\0';
,  }
,
,  MLF ();
,
,  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 */
,  if (idfchar (* textp)) {
,	idfsearch = TRUE;
,	while (textp != begin_line && idfchar (* textp))
,		textp --;
,  }
,  else {
,	idfsearch = FALSE;
,	while (textp != begin_line && alpha (* textp) && !idfchar (* textp))
,		textp --;
,  }
,
,/* Go to the next char if we're not at the beginning of the line */
,/* At the beginning of the line, check whether to stay or to go to the word */
,  if (textp != begin_line && * textp != '\n')
,	textp ++;
,  else if (textp == begin_line &&
,	   (idfsearch == TRUE) ? !idfchar (* textp) /* : white_space (* t.) */
,			: (!alpha (* textp) || idfchar (* textp))) {
,	textp ++;
,	if (white_space (* textp) || textp == start_pos)
,		/* no word there or not moved, so go back */
,		textp --;
,  }
,
,/* Find the x-coordinate of this address, and move to it */
,  move_address (textp, y);
,  if (remove == DELETE)
,	delete_text (cur_line, textp, cur_line, start_pos);
,}
,
,/*
, * MNW () 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'.
, */
,MNW ()
,{
,  if (hop_flag > 0) return ESEN ();
,  move_next_word (NO_DELETE);
,}
,
,move_next_word (remove)
,  FLAG remove;
,{
,  register char * textp = cur_text;
,
,  if ((remove == DELETE) && (viewonly == TRUE))
,	return viewonlyerr ();
,
,/* Move to the end of the current word. */
,  if (idfchar (* textp))
,    while (* textp != '\n' && idfchar (* textp))
,	textp ++;
,  else
,    while (alpha (* textp) && !idfchar (* 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_text (cur_line, cur_text, cur_line, textp);
,	return;
,  }
,
,/* If we're at end of line, move to the beginning of (first word on) the next line */
,  if (* textp == '\n' && cur_line->next != tail) {
,	MDN ();
,	move_to (LINE_START, y);
,	textp = cur_text;
,/*	while (* textp != '\n' && white_space (* textp))
,/*		textp ++;	/**/
,  }
,  move_address (textp, y);
,}
,
,/*
, * BSEN () and ESEN () look for the beginning or end of the current sentence.
, */
,BSEN ()
,{
,  search_for ("[;.]", REVERSE);
,}
,
,ESEN ()
,{
,  search_for ("[;.]", FORWARD);
,}
,
,/*
, * 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;
, */
,find_y (match_line)
,  LINE * match_line;
,{
,  return find_y_RD (match_line, TRUE);
,}
,
,find_y_w_o_RD (match_line)
,  LINE * match_line;
,{
,  return find_y_RD (match_line, FALSE);
,}
,
,find_y_RD (match_line, redrawflag)
,  LINE * match_line;
,  FLAG redrawflag;
,{
,  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);
,  if (redrawflag == TRUE) RD ();
,
,  return count;
,}
,
,/*
, * Dummy_line () adds an empty line at the end of the file. This is sometimes
, * useful in combination with the EF and MDN command in combination with the
, * Yank command set.
, * !!! I see no use for this and I don't consider such autonomous 
, * !!! modifications of the text (without user request) acceptable. TW.
, */
,#ifdef UNUSED
,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;
,	}
,}
,#endif UNUSED
,
,/*  ==================================================================	*
, *				Modify Commands				*
, *  ==================================================================	*/
,
,/*
, * viewonlyerr () outputs an error message with a beep
, */
,viewonlyerr ()
,{
,  ring_bell ();
,  error ("View only mode", NIL_PTR);
,  return ERRORS;
,}
,
,/*
, * 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.
, */
,DCC ()
,{
,  if (* cur_text == '\n')
,	if (cur_line->next == tail)
,	   return;
,	else
,	   delete_text (cur_line, cur_text, cur_line->next, cur_line->next->text);
,  else
,	delete_text (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.
, */
,DPC ()
,{
,  hop_flag = 0;
,
,  if (x == 0 && cur_line->prev == header)
,	return;			/* Top of file */
,
,  if (viewonly == TRUE)
,	return viewonlyerr ();
,
,  MLF ();			/* 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.
, */
,DLN ()
,{
,  if (* cur_text == '\n')
,	DCC ();
,  else
,	delete_text (cur_line, cur_text, cur_line, cur_text + length_of (cur_text) - 1);
,}
,
,/*
, * DNW () deletes the next word (as defined in MNW ())
, */
,DNW ()
,{
,  if (* cur_text == '\n')
,	DCC ();
,  else
,	move_next_word (DELETE);
,}
,
,/*
, * DPW () deletes the previous word (as defined in MPW ())
, */
,DPW ()
,{
,  if (cur_text == cur_line->text)
,	DPC ();
,  else
,	move_previous_word (DELETE);
,}
,
,/*
, * Insert character `character' at current location.
, */
,SNL ()
,{
,  S ('\n');
,}
,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 */
,		if (can_add_line == TRUE) {
,			add_line (y + 1);
,			clear_status ();
,			display (0, y, cur_line, 1);
,		}
,		else	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);
,  }
,}
,
,/*
, * CTRl inserts a control-char at the current location. A message that this
, * function is called is displayed at the status line.
, */
,CTRl ()
,{
,  register u_char ctrl;
,
,  status_msg ("Enter control character...");
,  ctrl = readchar ();
,  clear_status ();
,  if ((ctrl == '\177') || (ctrl == '?')) return S ('\177');
,  ctrl = ctrl & '\237';
,  if (ctrl == '\0') error ("Can't handle NULL char - not inserted", NIL_PTR);
,  else S (ctrl);
,}
,
,/*
, * LIB insert a line at the current position and moves back to the end of
, * the previous line.
, */
,LIB ()
,{
,  hop_flag = 0;
,
,  if (viewonly == TRUE)
,	return viewonlyerr ();
,
,  S ('\n');			/* Insert the line */
,  MUP ();			/* Move one line up */
,  move_to (LINE_END, y);	/* Move to end of this line */
,}
,
,/*
, * Install_line installs the buffer into a LINE structure it returns
, * a pointer to the allocated structure.
, */
,LINE *
,install_line (buffer, length)
,  char * buffer;
,  int length;
,{
,  register LINE * new_line = (LINE *) alloc (sizeof (LINE));
,
,  new_line->text = alloc (length + 1);
,  new_line->shift_count = 0;
,  copy_string (new_line->text, buffer);
,
,  return new_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.
, */
,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 (viewonly == TRUE)
,	return viewonlyerr ();
,
,  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 (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.
, */
,delete_text (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;
,  LINE * after_end = end_line->next;
,  int line_cnt = 0;			/* Nr of lines deleted */
,  int count = 0;
,  int shift = 0;			/* Used in shift calculation */
,  int nx = x;
,
,  if (viewonly == TRUE)
,	return viewonlyerr ();
,
,  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;
,  while (line != after_end && line != tail) {
,	/* Here, the original mined compared with end_line->next which has 
,	   already been discarded when the comparison should become true.
,	   This severe error remained undetected until I ported to MSDOS */
,	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 */
,/* !!! This resulted in a positioning error when a line containing TABs
,/* !!! was shifted back. So better leave it.
,/*  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);
,  if ((line_cnt <= SCREENMAX - y) && (can_delete_line == TRUE)) {
,	clear_status ();
,	display (0, y, start_line, 0);
,	line = proceed (start_line, SCREENMAX - y - line_cnt + 1);
,	while (line_cnt -- > 0) {
,		delete_line (y + 1);
,		if (line != tail) {
,			set_cursor (0, SCREENMAX);
,			line_print (line);
,			line = line->next;
,		}
,	}
,  }
,  else
,	display (0, y, start_line, shift - y);
,/*  move_to ((line_cnt == 1) ? nx : 0, y); */
,  move_to (nx, y);
,}
,
,/*  ==================================================================	*
, *				Yank Commands				*
, *  ==================================================================	*/
,
,LINE * mark_line = NIL_LINE;		/* For marking position. */
,char * mark_text = NIL_PTR;
,int lines_saved;			/* Nr of lines in buffer */
,
,/*
, * PT () inserts the buffer at the current location.
, */
,PT ()
,{
,  register int fd;		/* File descriptor for buffer */
,
,  if (viewonly == TRUE)
,	return viewonlyerr ();
,
,  if (hop_flag > 0) {
,	if ((fd = open (yankie_file, O_RDONLY, 0)) < 0) {
,		error ("No inter window buffer present.", NIL_PTR);
,		return;
,	}
,  }
,  else if ((fd = scratch_file (READ, FALSE)) == ERRORS) {
,	error ("Buffer is empty.", NIL_PTR);
,	return;
,  }
,  /* Insert the buffer */
,/*	file_insert (fd, FALSE); /* could yield a positioning error */
,	file_insert (fd, TRUE);
,}
,
,/*
, * INF () prompt for a filename and inserts the file at the current location
, * in the file.
, */
,INF ()
,{
,  register int fd;		/* File descriptor of file */
,  char name [maxLINE_LEN];	/* Buffer for file name */
,
,  if (viewonly == TRUE)
,	return viewonlyerr ();
,
,/* Get the file name */
,  if (get_file ("Get and insert file:", name) != FINE)
,	return;
,  clear_status ();
,
,  if ((fd = open (name, O_RDONLY, 0)) < 0)
,	error ("Cannot open file: " /*, name */, serror ());
,  else {	/* Insert the file */
,	file_insert (fd, TRUE);	/* leave cursor at begin of insertion */
,  }
,}
,
,/*
, * File_insert () inserts the contents of an opened file (as given by
, * filedescriptor fd) at the current location.
, * After the insertion, if old_pos is TRUE, the cursor remains at the
, * start of the inserted text, if old_pos is FALSE, it is placed to
, * its end. If old_pos is FALSE, this works erroneously if the last line
, * inserted contains a TAB character!!
, */
,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_line_err1 = NIL_PTR;
,  get_line_err2 = NIL_PTR;
,
,/* 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);
,  }
,
,  (void) close (fd);
,
,/* If illegal lines were input, report */
,  if ((get_line_err1 != NIL_PTR) || (get_line_err2 != NIL_PTR)) {
,	ring_bell ();
,	error (get_line_err1, get_line_err2);
,	sleep (1);
,  }
,
,/* 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.
, */
,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;			/* Error check for write */
,  char file_name [maxLINE_LEN];	/* Output file name */
,  char * msg_doing; char * msg_done;
,
,/* Checkout the buffer */
,  if ((yank_fd = scratch_file (READ, FALSE)) == ERRORS) {
,	error ("Buffer is empty.", NIL_PTR);
,	return;
,  }
,
,/* Get file name */
,  if (get_file ((hop_flag > 0) ? "Append buffer to file:"
,			       : "Write buffer to file:", file_name) != FINE)
,	return;
,
,/* Create the new file or open previous file for appending */
,  if (hop_flag > 0) {
,    if ((new_fd = open (file_name, O_WRONLY | O_CREAT | O_APPEND, fprot)) < 0) {
,	error ("Cannot append to file: ", serror ());
,	return;
,    }
,    msg_doing = "Appending "; msg_done = "Appended";
,  }
,  else {
,    if (checkoverwrite (file_name) != TRUE)
,	return ERRORS;
,    else if ((new_fd = creat (file_name, fprot)) < 0) {
,	error ("Cannot create file: ", serror ());
,	return;
,    }
,    msg_doing = "Writing "; msg_done = "Wrote";
,  }
,
,  status_line (msg_doing, file_name);
,
,/* 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 (msg_done, chars_saved, file_name, lines_saved, TRUE, FALSE, FALSE);
,}
,
,/*
, * MARK sets mark_line / mark_text to the current line / current text pointer.
, */
,MARK ()
,{
,  if (hop_flag > 0) return GOMA ();
,  mark_line = cur_line;
,  mark_text = cur_text;
,  status_msg ("Mark set");
,}
,
,/*
, * GOMA moves to the marked position
, */
,GOMA ()
,{
,  FLAG checkmark ();
,
,  if (checkmark () == NOT_VALID)
,	return error ("Mark not set.", NIL_PTR);
,  else
,	move_address (mark_text, find_y (mark_line));
,}
,
,/*
, * YA () puts the text between the marked position and the current
, * in the buffer.
, */
,YA ()
,{
,  set_up (NO_DELETE, (hop_flag > 0) ? TRUE : FALSE);
,}
,
,/*
, * DT () is essentially the same as YA (), but in DT () the text is deleted.
, */
,DT ()
,{
,  if (viewonly == TRUE)
,	return viewonlyerr ();
,
,  set_up (DELETE, (hop_flag > 0) ? TRUE : FALSE);
,}
,
,/*
, * 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.
, */
,set_up (remove, append)
,  FLAG remove;	/* == DELETE if text should be deleted */
,  FLAG append;	/* == TRUE if text should only be appended to yank buffer */
,{
,  FLAG checkmark ();
,
,  switch (checkmark ()) {
,	case NOT_VALID :
,		error ("Mark not set.", NIL_PTR);
,		return;
,	case SMALLER :
,		yank (mark_line, mark_text, cur_line, cur_text, remove, append);
,		yankie ();
,		break;
,	case BIGGER :
,		yank (cur_line, cur_text, mark_line, mark_text, remove, append);
,		yankie ();
,		break;
,	case SAME :		/* Ignore stupid behaviour */
,	/*	yank_status = EMPTY;	*/
,		chars_saved = 0L;
,		status_msg ("Nothing to save");
,		break;
,  }
,}
,
,/*
, * Yankie () provides a reference to the last saved buffer to be read
, * by other mined invocations.
, */
,yankie ()
,{
,  delete_file (yankie_file);
,#ifdef unix
,  link (yank_file, yankie_file);
,#else
,  build_string (text_buffer, copycommand, yank_file, yankie_file);
,  system (text_buffer);
,#endif
,}
,
,/*
, * 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.
, */
,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).
, */
,yank (start_line, start_textp, end_line, end_textp, remove, append)
,  LINE * start_line, * end_line;
,  char * start_textp, * end_textp;
,  FLAG remove;	/* == DELETE if text should be deleted */
,  FLAG append;	/* == TRUE if text should only be appended to yank buffer */
,{
,  register LINE * line = start_line;
,  register char * textp = start_textp;
,  int fd;
,
,/* Create file to hold buffer */
,  if ((fd = scratch_file (WRITE, append)) == ERRORS)
,	return;
,
,  chars_saved = 0L;
,  lines_saved = 0;
,  if (append == TRUE)
,	status_msg ("Appending text ...");
,  else	status_msg ("Saving text ...");
,
,/* Keep writing chars until the end_location is reached. */
,  while (textp != end_textp) {
,	if (writechar (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. In case it should,
,   * the following hack is used to save a lot of code.
,   * First move back to the start_position (this might be the current
,   * location) and then delete the text.
,   * This might look a bit confusing to the user the first time.
,   * Delete () will fix the screen.
,   */
,  if (remove == DELETE) {
,	move_to (find_x (start_line, start_textp), find_y (start_line));
,	delete_text (start_line, start_textp, end_line, end_textp);
,	mark_line = cur_line;
,	mark_text = cur_text;
,  }
,
,  if (append == TRUE)
,	status_line (num_out (chars_saved), " characters appended to buffer.");
,  else	status_line (num_out (chars_saved), " characters saved in buffer.");
,}
,
,/*
, * Scratch_file () tries to create a unique file in a temporary directory.
, * It tries several different filenames until one can be created
, * or MAXTRIALS attempts have been made.
, * After MAXTRIALS times, an error message is given and ERRORS is returned.
, */
,
,#define MAXTRIALS 99
,
,scratch_file (mode, append)
,  FLAG mode;	/* Can be READ or WRITE permission */
,  FLAG append;	/* == TRUE if text should only be appended to yank buffer */
,{
,  static int trials = 0;	/* Keep track of trials */
,  register char * y_ptr, * n_ptr;
,  int fd = 0;			/* 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. */
,#ifdef msdos
,	build_string (yank_file, "%s.%d", yankie_file, trials);
,#else
,	build_string (yank_file, "%s.%d.%d", yankie_file, getpid (), trials);
,#endif
,	/* Check file existence */
,	if (access (yank_file, 0 /* F_OK */) == 0
,	    || (fd = creat (yank_file, fprot)) < 0) {
,		if (++ trials >= MAXTRIALS) {
,		    build_string (text_buffer, "Unable to create scratchfile %s: ", yank_file);
,		    if (fd == 0)
,			error (text_buffer, "File exists");
,		    else
,			error (text_buffer, serror ());
,		    return ERRORS;
,		}
,		else
,		    return scratch_file (mode, append);	/* try again */
,	}
,  }
,  else if (yank_status == NOT_VALID && mode == READ) {
,	return ERRORS;
,  }
,  else /* yank_status == VALID */
,	if (  (mode == READ && (fd = open (yank_file, O_RDONLY, 0)) < 0)
,	   || (mode == WRITE &&
,		(fd = open (yank_file, O_WRONLY | O_CREAT |
,			((append == TRUE) ? O_APPEND : O_TRUNC), fprot)) < 0)) {
,	yank_status = NOT_VALID;
,	return ERRORS;
,  }
,
,  clear_buffer ();
,  return fd;
,}
,
,/*  ==================================================================	*
, *				Search Commands				*
, *  ==================================================================	*/
,
,/*
, * 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 [maxLINE_LEN];	/* Holds previous search expression */
,
,/*
, * SFW searches forward for an expression.
, */
,SFW ()
,{
,  search ("Search forward:", FORWARD);
,}
,
,/*
, * SRV searches backwards for an expression.
, */
,SRV ()
,{
,  search ("Search reverse:", REVERSE);
,}
,
,/*
, * RS searches using the last search direction and expression.
, */
,RS ()
,{
,  re_search ();
,}
,
,/*
, * 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 [maxLINE_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 search 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;
,}
,
,REGEX *
,make_expression (expr)
,  char * expr;
,{
,  static REGEX program;			/* Program of expression */
,
,  compile (expr, & 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.
, */
,GR ()
,{
,  change ("Global replace:", VALID, FALSE);
,}
,
,/*
, * Replace is substitute with confirmation dialogue.
, */
,REPL ()
,{
,  change ("Global replace (with confirm):", VALID, TRUE);
,}
,
,/*
, * LR () replaces all matches on the current line.
, */
,LR ()
,{
,  change ("Line replace:", NOT_VALID, FALSE);
,}
,
,/*
, * Change () prompts for an expression and a substitution pattern and changes
, * all matches of the expression into the substitution.
, * change () starts looking for expressions at the current line and
, * continues until the end of the file if the FLAG file is VALID.
, * For a call graph of search procedures see search ().
, */
,change (message, global, confirm)
,  char * message;		/* Message to prompt for expression */
,  FLAG global, confirm;
,{
,  char mess_buf [maxLINE_LEN];		/* Buffer to hold message */
,  char replacement [maxLINE_LEN];	/* Buffer to hold subst. pattern */
,  REGEX * program;			/* Program resulting from compilation */
,  register LINE * line = cur_line;
,  register char * textp;
,  char * substitute ();
,  long lines = 0L;		/* Nr of lines on which subs occurred */
,  long subs = 0L;		/* Nr of subs made */
,  int ly = y;			/* Index to check if line is on screen */
,  char c;
,
,  if (viewonly == TRUE)
,	return viewonlyerr ();
,
,/* 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 {
,		if (confirm == TRUE) {
,			ly = find_y (line);
,			textp = program->start_ptr;
,			move_address (program->start_ptr, ly);
,			status_msg ("Replace ? (y/n)");
,			c = promptyn ();
,			clear_status ();
,			if (c == 'y') {
,			    subs ++;	/* Increment subs */
,			    if ((textp = substitute (line, program, replacement, confirm))
,							== NIL_PTR)
,				    return;	/* Line too long */
,			    set_cursor (0, ly);
,			    line_print (line);
,			}
,			else
,			    textp ++;
,		}
,		else {
,		    subs ++;	/* Increment subs */
,		    if ((textp = substitute (line, program, replacement, confirm))
,							== NIL_PTR)
,			return;	/* Line too long */
,		}
,	    } while (  (program->status & BEGIN_LINE) != BEGIN_LINE
,		    && (program->status & END_LINE) != END_LINE
,		    && line_check (program, textp, FORWARD)
,		    && quit == FALSE);
,	    /* Check to see if we can print the result */
,	    if (confirm == FALSE && ly <= SCREENMAX) {
,		set_cursor (0, ly);
,		line_print (line);
,	    }
,	}
,	if (ly <= SCREENMAX)
,		ly ++;
,	line = line->next;
,  } while (line != tail && global == 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 %ld substitutions on %ld lines.",
,				mess_buf, subs, lines);
,	status_msg (mess_buf);
,  }
,  else if (global == NOT_VALID && subs >= REPORT)
,	status_line (num_out (subs), " substitutions.");
,  else
,	clear_status ();
,  move_to (x, y);
,/*  if (quit == TRUE) swallow_dummy_quit_char (); */
,  quit = FALSE;
,}
,
,/*
, * 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, confirm)
,  LINE * line;
,  REGEX * program;
,  char * replacement;	/* Contains replacement pattern */
,  FLAG confirm;
,{
,  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 failed: resulted line too long", 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 ().
, * Re_search () searches using the last search program and direction.
,   Call graph of search procedures:
,RS ------------\
,SFW -\		; re_search ; match -----------\
,SRV --; search +				\
,	       \				 \
,	        ; get_expression ; compile	  ; line_check ; check_string
,GR  \	       /				 /
,REPL ; change +---------------------------------/
,LR  /	       \
,		\ substitute
,
, */
,REGEX * lastprogram = NIL_REG;
,FLAG lastmethod = NOT_VALID;	/* FORWARD, REVERSE */
,
,search (message, method)
,  char * message;
,  FLAG method;
,{
,  register REGEX * program;
,
,  lastmethod = method;
,
,/* Get the expression */
,  if ((program = get_expression (message)) == NIL_REG)
,	return;
,  lastprogram = program;
,  re_search ();
,}
,
,search_for (expr, method)
,  char * expr;
,  FLAG method;
,{
,  register REGEX * program;
,
,/* make the expression */
,  if ((program = make_expression (expr)) == NIL_REG)
,	return;
,  do_search (program, method);
,}
,
,re_search ()
,{
,  do_search (lastprogram, lastmethod);
,}
,
,do_search (program, method)
,  register REGEX * program;
,  FLAG method;
,{
,  register LINE * match_line;
,
,  if (method == NOT_VALID) return error ("No previous search.", NIL_PTR);
,  if (program == NIL_REG) return error ("No previous search expression.", NIL_PTR);
,
,  set_cursor (0, YMAX);
,  clear_status ();
,  flush ();
,/* Find the match */
,  if ((match_line = match (program, cur_text, method)) == NIL_LINE) {
,	if (quit == TRUE) {
,		status_msg ("Aborted");
,/*		swallow_dummy_quit_char (); */
,		quit = FALSE;
,	}
,	else
,		status_msg ("Pattern not found");
,	return;
,  }
,
,/*  clear_status (); */
,  move_address (program->start_ptr, find_y (match_line));
,}
,
,/* Now the search and replace utilities */
,/* ------------------------------------ */
,/* 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 () if something went wrong. It sets 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.
, */
,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);
,}
,
,/*
, * Bcopy copies `bytes' bytes from the `from' address into the `to' address.
, */
,#ifdef vms
,#define defbcopy
,#endif
,#ifdef msdos
,#define defbcopy
,#endif
,#ifdef defbcopy	/* otherwise also in standard library */
,bcopy (from, to, bytes)
,  register char * from, * to;
,  register int bytes;
,{
,  while (bytes --)
,	* to ++ = * from ++;
,}
,#endif
,
,/*
, * 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.
, */
,compile (pattern, program)
,  register u_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 */
,  u_char low_char;		/* Index for chars in BRACKET */
,  u_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 (; ;) {	c = * pattern ++;
,	switch (c) {
,	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
,					&& low_char != '\0')
,				 /* avoid wrap-around beyond '\377' (loop!) */
,					* 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;
,  u_char * string;
,  register FLAG method;
,{
,  register LINE * line = cur_line;
,  u_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 */
,		status_msg ("Search wrapped around end of file");
,		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.
, */
,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_string () checks if 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 () returns MATCH for a match, NO_MATCH if the string
, * couldn't be matched or REG_ERROR for an illegal opcode in expression.
, */
,check_string (program, string, expression)
,  REGEX * program;
,  register u_char * string;
,  int * expression;
,{
,  register int opcode;		/* Holds opcode of next expr. atom */
,  u_char c;			/* Char that must be matched */
,  u_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)) != 0) {  /* 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 search program", NIL_PTR);
,	}
,	if (star_fl)
,		return star (program, mark, string, expression);
,  }
,  if (* expression & DONE) {
,	program->end_ptr = (char *) 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 == (u_char *) 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.
, */
,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.
, */
,in_list (list, c, list_length, opcode)
,  register int * list;
,  u_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;
,}
,
,/*  ==================================================================	*
, *				End					*
, *  ==================================================================	*/
EOSED

exit 0