/*
 * The routines in this file implement commands that work word or a
 * paragraph at a time.  There are all sorts of word mode commands.  If I
 * do any sentence mode commands, they are likely to be put in this file. 
 *
 * $Header: /usr2/foxharp/src/pgf/vile/RCS/word.c,v 1.42 1994/11/29 04:02:03 pgf Exp $
 *
 */

#include	"estruct.h"
#include	"edef.h"

/* Word wrap on n-spaces. Back-over whatever precedes the point on the current
 * line and stop on the first word-break or the beginning of the line. If we
 * reach the beginning of the line, jump back to the end of the word and start
 * a new line.	Otherwise, break the line at the word-break, eat it, and jump
 * back to the end of the word.
 * Returns TRUE on success, FALSE on errors.
 */
/* ARGSUSED */
int
wrapword(f,n)
int f,n;
{
	register int cnt;	/* size of word wrapped to next line */
	register int c;		/* character temporary */

	/* backup from the <NL> 1 char */
	if (!backchar(FALSE, 1))
		return(FALSE);

	/* back up until we aren't in a word,
	   make sure there is a break in the line */
	cnt = 0;
	while (c = char_at(DOT), !isspace(c)) {
		cnt++;
		if (!backchar(FALSE, 1))
			return(FALSE);
		/* if we make it to the beginning, start a new line */
		if (DOT.o == 0) {
			(void)gotoeol(FALSE, 0);
			return(lnewline());
		}
	}

	/* delete the forward white space */
	if (!ldelete(1L, FALSE))
		return(FALSE);

	/* put in a end of line */
	if (!newline(TRUE,1))
		return FALSE;

	/* and past the first word */
	while (cnt-- > 0) {
		if (forwchar(FALSE, 1) == FALSE)
			return(FALSE);
	}
	return(TRUE);
}


/*
 * Implements the vi "w" command.
 *
 * Move the cursor forward by the specified number of words. All of the motion
 * is done by "forwchar". Error if you try and move beyond the buffer's end.
 *
 * Returns of SORTOFTRUE result if we're doing a non-delete operation.  
 * Whitespace after a word is always included on deletes (and non-operations,
 * of course), but only on intermediate words for other operations, for
 * example.  The last word of non-delete ops does _not_ include its whitespace.
 */
int
forwviword(f, n)
int f,n;
{
	int s;

	if (n < 0)
		return (backword(f, -n));
	setchartype();
	if (forwchar(FALSE, 1) == FALSE)
		return (FALSE);
	while (n--) {
		int any = 0;
		while (((s = isnewviwordf()) == FALSE) || 
				(s == SORTOFTRUE && n != 0)) {
			if (forwchar(FALSE, 1) == FALSE)
				return (any != 0);
			any++;
		}
	}
	return TRUE;
}

/*
 * Implements the vi "W" command.
 *
 * Move the cursor forward by the specified number of words. All of the motion
 * is done by "forwchar". Error if you try and move beyond the buffer's end.
 */
int
forwword(f, n)
int f,n;
{
	int s;

	if (n < 0)
		return (backword(f, -n));
	setchartype();
	if (forwchar(FALSE, 1) == FALSE)
		return (FALSE);
	while (n--) {
		int any = 0;
		while (((s = isnewwordf()) == FALSE) || 
				(s == SORTOFTRUE && n != 0)) {
			if (forwchar(FALSE, 1) == FALSE)
				return (any != 0);
			any++;
		}
	}
	return(TRUE);
}

/*
 * Implements the vi "e" command.
 *
 * Move the cursor forward by the specified number of words. All of the motion
 * is done by "forwchar". Error if you try and move beyond the buffer's end.
 */
int
forwviendw(f, n)
int f,n;
{
	int s = FALSE;
	if (!f)
		n = 1;
	else if (n <= 0)
		return (FALSE);
	if (forwchar(FALSE, 1) == FALSE)
		return (FALSE);
	setchartype();
	while (n--) {
		int	any = 0;
		while ((s = isendviwordf()) == FALSE) {
			if (forwchar(FALSE, 1) == FALSE)
				return (any != 0);
			any++;
		}

	}
	if (s == SORTOFTRUE)
		return TRUE;
	else
		return backchar(FALSE, 1);
}

/*
 * Implements the vi "E" command.
 *
 * Move the cursor forward by the specified number of words. All of the motion
 * is done by "forwchar". Error if you try and move beyond the buffer's end.
 */
int
forwendw(f, n)
int f,n;
{
	int s = FALSE;
	if (!f)
		n = 1;
	else if (n <= 0)
		return (FALSE);
	if (forwchar(FALSE, 1) == FALSE)
		return (FALSE);
	setchartype();
	while (n--) {
		int	any = 0;
		while ((s = isendwordf()) == FALSE) {
			if (forwchar(FALSE, 1) == FALSE)
				return (any != 0);
			any++;
		}

	}
	if (s == SORTOFTRUE)
		return TRUE;
	else
		return backchar(FALSE, 1);
}

/*
 * Implements the vi "b" command.
 *
 * Move the cursor backward by "n" words. All of the details of motion are
 * performed by the "backchar" and "forwchar" routines. Error if you try to
 * move beyond the buffers.
 */
int
backviword(f, n)
int f,n;
{
	if (n < 0)
		return (forwword(f, -n));
	if (backchar(FALSE, 1) == FALSE)
		return (FALSE);
	setchartype();
	while (n--) {
		int	any = 0;
		while (isnewviwordb() == FALSE) {
			any++;
			if (backchar(FALSE, 1) == FALSE)
				return (any != 0);
		}
	}
	return (forwchar(FALSE, 1));
}

/*
 * Implements the vi "B" command.
 *
 * Move the cursor backward by "n" words. All of the details of motion are
 * performed by the "backchar" and "forwchar" routines. Error if you try to
 * move beyond the buffers.
 */
int
backword(f, n)
int f,n;
{
	if (n < 0)
		return (forwword(f, -n));
	if (backchar(FALSE, 1) == FALSE)
		return (FALSE);
	setchartype();
	while (n--) {
		int	any = 0;
		while (isnewwordb() == FALSE) {
			any++;
			if (backchar(FALSE, 1) == FALSE)
				return (any != 0);
		}
	}
	return (forwchar(FALSE, 1));
}

int
joinregion()
{
	register int status;
	register int doto, c;
	LINE	*end;
	REGION	region;
	int	done = FALSE;

	if ((status = getregion(&region)) == TRUE
	 && (status = !is_last_line(region.r_orig, curbp)) == TRUE) {

		DOT = region.r_orig;
		end = l_ref(region.r_end.l);
		regionshape = EXACT;

		while (!done && status == TRUE) {
			c = EOS;
			(void)gotoeol(FALSE,1);
			if (DOT.o > 0)
				c = lGetc(DOT.l, DOT.o-1);
			(void)setmark();
			if (forwline(FALSE,1) != TRUE)
				break;
			(void)firstnonwhite(FALSE,1);

			done = ((l_ref(DOT.l) == end) || (lForw(DOT.l) == end));
			if (killregionmaybesave(FALSE) != TRUE)
				break;

			doto = DOT.o;
			if (doto == 0)
				;	/* join at column 0 to empty line */
			else if (doto < lLength(DOT.l)) {
				if (lGetc(DOT.l, doto) == ')')
					;	/* join after parentheses */
				else if (lGetc(DOT.l, doto-1) == '.')
					status = linsert(2,' ');
				else if (!isspace(c))
					status = linsert(1,' ');
			}
		}
	}
	rls_region();
	return status;
}

int
joinlines(f,n)
int f,n;
{
	extern CMDFUNC f_godotplus;
	havemotion = &f_godotplus;
	return(operjoin(f,n));
}

#if OPT_FORMAT
int
formatregion()
{
	register int c;			/* current char during scan	*/
	register int wordlen;		/* length of current word	*/
	register int clength;		/* position on line during fill	*/
	register int i;			/* index during word copy	*/
	register int newlen;		/* tentative new line length	*/
	register int finished;		/* Are we at the End-Of-Paragraph? */
	register int firstflag;		/* first word? (needs no space)	*/
	register int is_comment;	/* doing a comment block?	*/
	register int comment_char = -1;	/* # or *, for shell or C	*/
	register int at_nl = TRUE;	/* just saw a newline?		*/
	fast_ptr LINEPTR pastline;	/* pointer to line just past EOP */
	register int sentence;		/* was the last char a period?	*/
	char wbuf[NSTRING];		/* buffer for current word	*/
	int secondindent;
	regexp *expP, *expC;
	int s;
	
	if (!sameline(MK, DOT)) {
		REGION region;

		if (getregion(&region) != TRUE)
			return FALSE;
		if (sameline(region.r_orig, MK))
			swapmark();
		rls_region();
	}
	pastline = MK.l;
	if (!same_ptr(pastline, buf_head(curbp)))
		pastline = lFORW(pastline);

	expP = b_val_rexp(curbp,VAL_PARAGRAPHS)->reg;
	expC = b_val_rexp(curbp,VAL_COMMENTS)->reg;
 	finished = FALSE;
 	while (finished != TRUE) {  /* i.e. is FALSE or SORTOFTRUE */
		while (lregexec(expP, l_ref(DOT.l), 0, lLength(DOT.l)) ||
			lregexec(expC, l_ref(DOT.l), 0, lLength(DOT.l)) ) {
			DOT.l = lFORW(DOT.l);
			if (same_ptr(DOT.l, pastline))
				return setmark();
		}

		secondindent = indentlen(l_ref(DOT.l));
		
		/* go forward to get the indent for the second
			and following lines */
		DOT.l = lFORW(DOT.l);

		if (!same_ptr(DOT.l, pastline)) {
			secondindent = indentlen(l_ref(DOT.l));
		}
			
		/* and back where we should be */
		DOT.l = lBACK(DOT.l);
		(void)firstnonwhite(FALSE,1);
		
		clength = indentlen(l_ref(DOT.l));
		wordlen = 0;
		sentence = FALSE;

		is_comment = ( ((c = char_at(DOT)) == '#') ||
				(c == '>') ||
				(c == '*') ||
				((c == '/') &&
				DOT.o+1 < lLength(DOT.l) &&
				 lGetc(DOT.l,DOT.o+1) == '*'));

		if (is_comment)
			comment_char = (c == '#' || c == '>') ? c :'*';

		/* scan through lines, filling words */
		firstflag = TRUE;
		finished = FALSE;
		while (finished == FALSE) { /* i.e. is not TRUE  */
					    /* or SORTOFTRUE */
			if (interrupted()) return ABORT;

			/* get the next character */
			if (is_at_end_of_line(DOT)) {
				c = ' ';
				DOT.l = lFORW(DOT.l);
				if (same_ptr(DOT.l, pastline)) {
					finished = TRUE;
				} else if (
				lregexec(expP, l_ref(DOT.l), 0, lLength(DOT.l)) ||
				lregexec(expC, l_ref(DOT.l), 0, lLength(DOT.l))) {
					/* we're at a section break */
					finished = SORTOFTRUE;
				}
				DOT.l = lBACK(DOT.l);
				at_nl = TRUE;
			} else {
				c = char_at(DOT);
				if (at_nl && ( isspace(c) ||
					(is_comment && c == comment_char)))
					c = ' ';
				else
					at_nl = FALSE;
			}
			/* and then delete it */
			if (finished == FALSE) {
				s = ldelete(1L, FALSE);
				if (s != TRUE) return s;
			}

			/* if not a separator, just add it in */
			if (!isblank(c)) {
				/* was it the end of a "sentence"? */
				sentence = (c == '.' || c == '?' || c == '!');
				if (wordlen < NSTRING - 1)
					wbuf[wordlen++] = c;
			} else if (wordlen) {
				/* at a word break with a word waiting */
				/* calculate tentative new length
							with word added */
				newlen = clength + 1 + wordlen;
				if (newlen <= b_val(curbp,VAL_FILL)) {
					/* add word to current line */
					if (!firstflag) {
						/* the space */
						s = linsert(1, ' ');
						if (s != TRUE) return s;
						++clength;
					} 
				} else {
					/* fix the leading indent now, if
						some spaces should be tabs */
					if (b_val(curbp,MDTABINSERT))
						entabline((void *)TRUE, 0, 0);
			                if (lnewline() == FALSE)
						return FALSE;
				        if (linsert(secondindent,' ') == FALSE)
						return FALSE;
					clength = secondindent;
					firstflag = TRUE;
				}
				if (firstflag && is_comment &&
						strncmp("/*",wbuf,2)) {
					s = linsert(1, comment_char);
					if (s != TRUE) return s;
					s = linsert(1, ' ');
					if (s != TRUE) return s;
					clength += 2;
				}
				firstflag = FALSE;

				/* and add the word in in either case */
				for (i=0; i<wordlen; i++) {
					s = linsert(1, wbuf[i]);
					if (s != TRUE) return s;
					++clength;
				}
				if (finished == FALSE && sentence) {
					s = linsert(1, ' ');
					if (s != TRUE) return s;
					++clength;
				}
				wordlen = 0;
			}
		}
		/* catch the case where we're done with a line not because
		  there's no more room, but because we're done processing a
		  section or the region */
		if (b_val(curbp,MDTABINSERT))
			entabline((void *)TRUE, 0, 0);
		DOT.l = lFORW(DOT.l);
	}
	return setmark();
}
#endif /* OPT_FORMAT */


#if	OPT_WORDCOUNT
/*	wordcount:	count the # of words in the marked region,
			along with average word sizes, # of chars, etc,
			and report on them.			*/
int
wordcount(f, n)
{
	register LINE *lp;	/* current line to scan */
	register int offset;	/* current char to scan */
	long size;		/* size of region left to count */
	register int ch;	/* current character to scan */
	register int wordflag;	/* are we in a word now? */
	register int lastword;	/* were we just in a word? */
	long nwords;		/* total # of words */
	long nchars;		/* total number of chars */
	int nlines;		/* total number of lines in region */
	int avgch;		/* average number of chars/word */
	int status;		/* status return code */
	REGION region;		/* region to look at */

	/* make sure we have a region to count */
	if ((status = getregion(&region)) != TRUE) {
		rls_region();
		return(status);
	}
	lp     = l_ref(region.r_orig.l);
	offset = region.r_orig.o;
	size   = region.r_size;

	/* count up things */
	lastword = FALSE;
	nchars = 0L;
	nwords = 0L;
	nlines = 0;
	while (size--) {

		/* get the current character */
		if (offset == llength(lp)) {	/* end of line */
			ch = '\n';
			lp = lforw(lp);
			offset = 0;
			++nlines;
		} else {
			ch = lgetc(lp, offset);
			++offset;
		}

		/* and tabulate it */
		if (((wordflag = isident(ch)) != 0) && !lastword)
			++nwords;
		lastword = wordflag;
		++nchars;
	}

	/* and report on the info */
	if (nwords > 0L)
		avgch = (int)((100L * nchars) / nwords);
	else
		avgch = 0;

	mlforce("lines %d, words %D, chars %D, avg chars/word %f",
		nlines, nwords, nchars, avgch);

	rls_region();
	return(TRUE);
}
#endif
