/*
 * ========================================================================
 * Copyright 2013-2022 Eduardo Chappa
 * Copyright 2006-2007 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

/*
 * Program:	Word at a time routines
 *
 * The routines in this file implement commands that work word at a time.
 * There are all sorts of word mode commands. If I do any sentence and/or
 * paragraph mode commands, they are likely to be put in this file.
 */

#include	"headers.h"


int fpnewline(UCS *quote);
int fillregion(UCS *qstr, REGION *addedregion);
int setquotelevelinregion(int quotelevel, REGION *addedregion);
int is_user_separator(UCS c);


/* 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.
 */
int
wrapword(void)
{
    register int cnt;			/* size of word wrapped to next line */
    register int bp;			/* index to wrap on */
    register int first = -1;
    int wid, ww;

    if(curwp->w_doto <= 0)		/* no line to wrap? */
      return(FALSE);

    wid = 0;
    for(bp = cnt = 0; cnt < llength(curwp->w_dotp) && !bp; cnt++){
	if(ucs4_isspace(lgetc(curwp->w_dotp, cnt).c)){
	    first = 0;
	    if(lgetc(curwp->w_dotp, cnt).c == TAB){
	      ++wid;
	      while(wid & 0x07)
		++wid;
	    }
	    else
	      ++wid;
	}
	else{
	    ww = wcellwidth((UCS) lgetc(curwp->w_dotp, cnt).c);
	    wid += (ww >= 0 ? ww : 1);
	    if(!first)
	      first = cnt;
	}

	if(first > 0 && wid > fillcol)
	  bp = first;
    }

    if(!bp)
      return(FALSE);

    /* bp now points to the first character of the next line */
    cnt = curwp->w_doto - bp;
    curwp->w_doto = bp;

    if(!lnewline())			/* break the line */
      return(FALSE);

    /*
     * if there's a line below, it doesn't start with whitespace 
     * and there's room for this line...
     */
    if(!(curbp->b_flag & BFWRAPOPEN)
       && lforw(curwp->w_dotp) != curbp->b_linep 
       && llength(lforw(curwp->w_dotp)) 
       && !ucs4_isspace(lgetc(lforw(curwp->w_dotp), 0).c)
       && (llength(curwp->w_dotp) + llength(lforw(curwp->w_dotp)) < fillcol)){
	gotoeol(0, 1);			/* then pull text up from below */
	if(lgetc(curwp->w_dotp, curwp->w_doto - 1).c != ' ')
	  linsert(1, ' ');

	forwdel(0, 1);
	gotobol(0, 1);
    }

    curbp->b_flag &= ~BFWRAPOPEN;	/* don't open new line next wrap */
					/* restore dot (account for NL)  */
    if(cnt && !forwchar(0, cnt < 0 ? cnt-1 : cnt))
      return(FALSE);

    return(TRUE);
}


/*
 * 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(int f, int n)
{
        if (n < 0)
                return (forwword(f, -n));
        if (backchar_no_header_editor(FALSE, 1) == FALSE)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (backchar_no_header_editor(FALSE, 1) == FALSE)
                                return (FALSE);
                }
                while (inword() != FALSE) {
                        if (backchar_no_header_editor(FALSE, 1) == FALSE)
                                return (FALSE);
                }
        }
        return (forwchar(FALSE, 1));
}

/*
 * 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(int f, int n)
{
        if (n < 0)
                return (backword(f, -n));
        while (n--) {
#if	NFWORD
                while (inword() != FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
#endif
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
#if	NFWORD == 0
                while (inword() != FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
#endif
        }
	return(TRUE);
}

int
ucs4_isalnum(UCS c)
{
    return((c && c <= 0x7f && isalnum((unsigned char) c))
	   || (c >= 0xA0 && !SPECIAL_SPACE(c)));
}

int
ucs4_isalpha(UCS c)
{
    return((c && c <= 0x7f && isalpha((unsigned char) c))
	   || (c >= 0xA0 && !SPECIAL_SPACE(c)));
}

int
ucs4_isspace(UCS c)
{
    return((c < 0xff && isspace((unsigned char) c)) || SPECIAL_SPACE(c));
}

int
ucs4_ispunct(UCS c)
{
     return !ucs4_isalnum(c) && !ucs4_isspace(c);
}

#ifdef	MAYBELATER
/*
 * Move the cursor forward by the specified number of words. As you move,
 * convert any characters to upper case. Error if you try and move beyond the
 * end of the buffer. Bound to "M-U".
 */
int
upperword(int f, int n)
{
        register int    c;
	CELL            ac;

	ac.a = 0;
	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/
        if (n < 0)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
                while (inword() != FALSE) {
                        c = lgetc(curwp->w_dotp, curwp->w_doto).c;
                        if (c>='a' && c<='z') {
                                ac.c = (c -= 'a'-'A');
                                lputc(curwp->w_dotp, curwp->w_doto, ac);
                                lchange(WFHARD);
                        }
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
        }
        return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words. As you move
 * convert characters to lower case. Error if you try and move over the end of
 * the buffer. Bound to "M-L".
 */
int
lowerword(int f, int n)
{
        register int    c;
	CELL            ac;

	ac.a = 0;
	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/
        if (n < 0)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
                while (inword() != FALSE) {
                        c = lgetc(curwp->w_dotp, curwp->w_doto).c;
                        if (c>='A' && c<='Z') {
                                ac.c (c += 'a'-'A');
                                lputc(curwp->w_dotp, curwp->w_doto, ac);
                                lchange(WFHARD);
                        }
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
        }
        return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words. As you move
 * convert the first character of the word to upper case, and subsequent
 * characters to lower case. Error if you try and move past the end of the
 * buffer. Bound to "M-C".
 */
int
capword(int f, int n)
{
        register int    c;
	CELL	        ac;

	ac.a = 0;
	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/
        if (n < 0)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                }
                if (inword() != FALSE) {
                        c = lgetc(curwp->w_dotp, curwp->w_doto).c;
                        if (c>='a' && c<='z') {
			    ac.c = (c -= 'a'-'A');
			    lputc(curwp->w_dotp, curwp->w_doto, ac);
			    lchange(WFHARD);
                        }
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        while (inword() != FALSE) {
                                c = lgetc(curwp->w_dotp, curwp->w_doto).c;
                                if (c>='A' && c<='Z') {
				    ac.c = (c += 'a'-'A');
				    lputc(curwp->w_dotp, curwp->w_doto, ac);
				    lchange(WFHARD);
                                }
                                if (forwchar(FALSE, 1) == FALSE)
                                        return (FALSE);
                        }
                }
        }
        return (TRUE);
}

/*
 * Kill forward by "n" words. Remember the location of dot. Move forward by
 * the right number of words. Put dot back where it was and issue the kill
 * command for the right number of characters. Bound to "M-D".
 */
int
delfword(int f, int n)
{
        register long   size;
        register LINE   *dotp;
        register int    doto;

	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/
        if (n < 0)
                return (FALSE);
        dotp = curwp->w_dotp;
        doto = curwp->w_doto;
        size = 0L;
        while (n--) {
#if	NFWORD
		while (inword() != FALSE) {
			if (forwchar(FALSE,1) == FALSE)
				return(FALSE);
			++size;
		}
#endif
                while (inword() == FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        ++size;
                }
#if	NFWORD == 0
                while (inword() != FALSE) {
                        if (forwchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        ++size;
                }
#endif
        }
        curwp->w_dotp = dotp;
        curwp->w_doto = doto;
        return (ldelete(size, kinsert));
}

/*
 * Kill backwards by "n" words. Move backwards by the desired number of words,
 * counting the characters. When dot is finally moved to its resting place,
 * fire off the kill command. Bound to "M-Rubout" and to "M-Backspace".
 */
int
delbword(int f, int n)
{
        register long   size;

	if (curbp->b_mode&MDVIEW)	/* don't allow this command if	*/
		return(rdonly());	/* we are in read only mode	*/
        if (n < 0)
                return (FALSE);
        if (backchar(FALSE, 1) == FALSE)
                return (FALSE);
        size = 0L;
        while (n--) {
                while (inword() == FALSE) {
                        if (backchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        ++size;
                }
                while (inword() != FALSE) {
                        if (backchar(FALSE, 1) == FALSE)
                                return (FALSE);
                        ++size;
                }
        }
        if (forwchar(FALSE, 1) == FALSE)
                return (FALSE);
        return (ldelete(size, kinsert));
}
#endif	/* MAYBELATER */

/*
 * Return TRUE if the character at dot is a character that is considered to be
 * part of a word.
 */
int
inword(void)
{
     if(curwp->w_doto < llength(curwp->w_dotp))
     {
         if(ucs4_isalnum(lgetc(curwp->w_dotp, curwp->w_doto).c))
         {
             return(TRUE);
         }
         else if(ucs4_ispunct(lgetc(curwp->w_dotp, curwp->w_doto).c)
	         && !is_user_separator(lgetc(curwp->w_dotp, curwp->w_doto).c))
         {
             if((curwp->w_doto > 0) &&
                 ucs4_isalnum(lgetc(curwp->w_dotp, curwp->w_doto - 1).c) &&
                 (curwp->w_doto + 1 < llength(curwp->w_dotp)) &&
                 ucs4_isalnum(lgetc(curwp->w_dotp, curwp->w_doto + 1).c))
             {
                 return(TRUE);
             }
         }
     }

     return(FALSE);
}


int
is_user_separator(UCS c)
{
    UCS *u;

    if(glo_wordseps)
      for(u = glo_wordseps; *u; u++)
	if(*u == c)
	  return 1;

    return 0;
}

void
do_quote_match(UCS *q, LINE *l, UCS *buf, size_t buflen)
{
    register int i, j;
    int qstart, qend, k;

    /* 
     * The method for determining the quote string is:
     * 1) strip leading and trailing whitespace from q 
     * 2) add all leading whitespace to buf
     * 3) check for q
     * 4) if q, append q to buf and any trailing whitespace
     * 5) repeat steps 3 and 4 as necessary
     *
     * q in the future could be made to be an array of (UCS *)'s
     *    (">" and whatever the user's quote_string is)
     */

    *buf = '\0';

    if(l == NULL)
      return;

    /* count leading whitespace as part of the quote */
    for(j = 0; j <= llength(l) && lgetc(l, j).c == ' ' && j+1 < buflen; j++)
        buf[j] = lgetc(l, j).c;
    buf[j] = '\0';   

    if(q == NULL || *q == '\0')
      return;

    /* pare down q so it contains no leading or trailing whitespace */
    for(i = 0; q[i] == ' '; i++);
    qstart = i;
    for(i = ucs4_strlen(q); i > 0 && q[i-1] == ' '; i--);
    qend = i;

    /* for quote strings that are blanks, chop buf to the length of q */
    if(qend <= qstart){
      if(ucs4_strlen(q) < buflen)
	buf[ucs4_strlen(q)] = '\0';
      return;
    }
          
    while(j <= llength(l)){
        for(i = qstart; j <= llength(l) && i < qend; i++, j++)
          if(q[i] != lgetc(l, j).c)
            return;
    
        if(i >= qend){
            if(ucs4_strlen(buf) + qend - qstart < (buflen - 1))
              ucs4_strncat(buf, q + qstart, qend - qstart);
        } 
    
        /*
         * if we're this far along, we've matched a quote string,
         * and should now add the following white space.
         */
        for(k = ucs4_strlen(buf);
		j <= llength(l) && lgetc(l,j).c == ' ' && (k + 1 < buflen);
            j++, k++){
            buf[k] = lgetc(l,j).c;
        }
        buf[k] = '\0';
        
        if(j > llength(l))
          return;
    }
}

/*
 * Return number of quotes if whatever starts the line matches the quote string
 */
int
quote_match(UCS *q, LINE *gl, UCS *bufl, size_t buflen)
{
    LINE *nl = gl != curbp->b_linep ? lforw(gl) : NULL;
    LINE *pl = lback(gl) != curbp->b_linep ? lback(gl) : NULL;
    UCS bufp[NSTRING], bufn[NSTRING];
    int i, j, qstart, qend;
    int quoted_line = 0;

    do_quote_match(q, pl, bufp, sizeof(bufp));	/* previous line */
    do_quote_match(q, gl, bufl, buflen);	/* given line	 */
    do_quote_match(q, nl, bufn, sizeof(bufn));	/* next line	 */

    if(!ucs4_strcmp(bufp, bufl) || !ucs4_strcmp(bufl, bufn))
      return ucs4_strlen(bufl);

    /* is this line quoted? */
    if(q && *q){
      /* pare down q so it contains no leading or trailing whitespace */
      for(i = 0; q[i] == ' '; i++);
      qstart = i;
      for(i = ucs4_strlen(q); i > 0 && q[i-1] == ' '; i--);
      qend = i;
      for(i = 0; i < llength(gl) 
		 && i + qstart < qend 
		 && lgetc(gl, i).c == q[i+qstart]; i++);
      if(i + qstart == qend)
         quoted_line = 1;
    }

    /* compare bufl and bufn */
    for(i = 0; bufl[i] && bufn[i] && bufl[i] == bufn[i]; i++);

    /* go back to last non-space character */
    for(; i > 0 && bufl[i-1] == ' '; i--);

    /* do bufl and bufn differ only in spaces? */
    for(j = i; bufl[j] && bufl[j] == ' '; j++);

    /* if they differ only on trailing spaces, chop bufl to agree with bufn */
    if (!bufl[j] )
       bufl[Pmaster && quoted_line ? (j > i ? i+1 : i) :  i] = '\0';

    return ucs4_strlen(bufl);
}


/* Justify the entire buffer instead of just a paragraph */
int
fillbuf(int f, int n)
{
    LINE *eobline;
    REGION region;

    if(curbp->b_mode&MDVIEW){		/* don't allow this command if	*/
	return(rdonly());		/* we are in read only mode	*/
    }
    else if (fillcol == 0) {		/* no fill column set */
	mlwrite_utf8("No fill column set", NULL);
	return(FALSE);
    }

    if((lastflag & CFFILL) && (lastflag & CFFLBF)){
	/* no use doing a full justify twice */
	thisflag |= (CFFLBF | CFFILL);
	return(TRUE);
    }

    /* record the pointer of the last line */
    if(gotoeob(FALSE, 1) == FALSE)
      return(FALSE);

    eobline = curwp->w_dotp;		/* last line of buffer */
    if(!llength(eobline))
      eobline = lback(eobline);

    /* and back to the beginning of the buffer */
    gotobob(FALSE, 1);

    thisflag |= CFFLBF; /* CFFILL also gets set in fillpara */

    if(!Pmaster)
      sgarbk = TRUE;
    
    curwp->w_flag |= WFMODE;

    /*
     * clear the kill buffer, that's where we'll store undo
     * information, we can't do the fill buffer because
     * fillpara relies on its contents
     */
    kdelete();
    curwp->w_doto = 0;
    getregion(&region, eobline, llength(eobline));

    /* Put full message in the kill buffer for undo */
    if(!ldelete(region.r_size, kinsert))
      return(FALSE);

    /* before yank'ing, clear lastflag so we don't just unjustify */
    lastflag &= ~(CFFLBF | CFFILL);

    /* Now in kill buffer, bring back text to use in fillpara */
    yank(FALSE, 1);

    gotobob(FALSE, 1);

    /* call fillpara until we're at the end of the buffer */
    while(curwp->w_dotp != curbp->b_linep)
      if(!(fillpara(FALSE, 1)))
	return(FALSE);
    
    return(TRUE);
}


/*
 * Fill the current paragraph according to the current fill column
 */
int
fillpara(int f, int n)
{
    UCS    *qstr, qstr2[NSTRING], c;
    int     quotelevel = -1;
    REGION  addedregion;
    char    action = 'P';

    if(curbp->b_mode&MDVIEW){		/* don't allow this command if	*/
	return(rdonly());		/* we are in read only mode	*/
    }
    else if (fillcol == 0) {		/* no fill column set */
	mlwrite_utf8("No fill column set", NULL);
	return(FALSE);
    }
    else if(curwp->w_dotp == curbp->b_linep && !curwp->w_markp) /* don't wrap! */
      return(FALSE);

    /*
     * If there is already a region set, then we may use it
     * instead of the current paragraph.
     */

    if(curwp->w_markp){
	int k, rv;
	KEYMENU menu_justify[12];
	char prompt[100];

	for(k = 0; k < 12; k++){
	    menu_justify[k].name = NULL;
	    KS_OSDATASET(&menu_justify[k], KS_NONE);
	}

	menu_justify[1].name  = "R";
	menu_justify[1].label = "[" N_("Region") "]";
	menu_justify[6].name  = "^C";
	menu_justify[6].label = N_("Cancel");
	menu_justify[7].name  = "P";
	menu_justify[7].label = N_("Paragraph");
	menu_justify[2].name  = "Q";
	menu_justify[2].label = N_("Quotelevel");

	wkeyhelp(menu_justify);		/* paint menu */
	sgarbk = TRUE;
	if(Pmaster && curwp)
	  curwp->w_flag |= WFMODE;

	strncpy(prompt, "justify Region, Paragraph; or fix Quotelevel ? ", sizeof(prompt));
	prompt[sizeof(prompt)-1] = '\0';
	mlwrite_utf8(prompt, NULL);
	(*term.t_rev)(1);
	rv = -1;
	while(1){
	    switch(c = GetKey()){

	      case (CTRL|'C') :		/* Bail out! */
	      case F2         :
		pputs_utf8(_("ABORT"), 1);
		rv = ABORT;
		emlwrite("", NULL);
		break;

	      case (CTRL|'M') :		/* default */
	      case 'r' :
	      case 'R' :
	      case F3  :
		pputs_utf8(_("Region"), 1);
		rv = 'R';
		break;

	      case 'p' :
	      case 'P' :
	      case F7  :
		pputs_utf8(_("Paragraph"), 1);
		rv = 'P';
		break;

	      case 'q' :
	      case 'Q' :
	      case F8  :
	      case '0' : case '1' : case '2' : case '3' : case '4' :
	      case '5' : case '6' : case '7' : case '8' : case '9' :
		pputs_utf8(_("Quotelevel"), 1);
		while(rv == -1){
		  switch(c){
		    case 'q' :
		    case 'Q' :
		    case F8  :
		     {char num[20];

		      num[0] = '\0';
		      switch(mlreplyd_utf8("Quote Level ? ", num, sizeof(num), QNORML, NULL)){
		        case TRUE:
			  if(isdigit(num[0])){
			      quotelevel = atoi(num);
			      if(quotelevel < 0){
				  emlwrite("Quote Level cannot be negative", NULL);
				  sleep(3);
			      }
			      else if(quotelevel > 20){
				  emlwrite("Quote Level should be less than 20", NULL);
				  rv = ABORT;
			      }
			      else{
				  rv = 'Q';
			      }
			  }
			  else if(num[0]){
			      emlwrite("Quote Level should be a number", NULL);
			      sleep(3);
			  }

			  break;

		        case HELPCH:
			  emlwrite("Enter the number of quotes you want before the text", NULL);
			  sleep(3);
			  break;

		        default:
			  emlwrite("Quote Level is a number", NULL);
			  rv = ABORT;
			  break;
		      }
		     }

		      break;

		    case '0' : case '1' : case '2' : case '3' : case '4' :
		    case '5' : case '6' : case '7' : case '8' : case '9' :
		      rv = 'Q';
		      quotelevel = (int) (c - '0');
		      break;
		  }
		}

		break;

	      case (CTRL|'G') :
		if(term.t_mrow == 0 && km_popped == 0){
		    movecursor(term.t_nrow-2, 0);
		    peeol();
		    term.t_mrow = 2;
		    (*term.t_rev)(0);
		    wkeyhelp(menu_justify);
		    mlwrite_utf8(prompt, NULL);
		    (*term.t_rev)(1);
		    sgarbk = TRUE;			/* mark menu dirty */
		    km_popped++;
		    break;
		}
		/* else fall through */

	      default:
		(*term.t_beep)();

	      case NODATA :
		break;
	    }

	    (*term.t_flush)();
	    if(rv != -1){
		(*term.t_rev)(0);
		if(km_popped){
		    term.t_mrow = 0;
		    movecursor(term.t_nrow, 0);
		    peeol();
		    sgarbf = 1;
		    km_popped = 0;
		}

		action = rv;
		break;
	    }
	}

	if(action != ABORT)
	  emlwrite("", NULL);
    }

    if(action == 'R' && curwp->w_markp){
	/* let yank() know that it may be restoring a paragraph */
	thisflag |= CFFILL;

	if(!Pmaster)
	  sgarbk = TRUE;
	
	curwp->w_flag |= WFMODE;

	swap_mark_and_dot_if_mark_comes_first();

	/* determine if we're justifying quoted text or not */
	qstr = quote_match(glo_quote_str, 
			curwp->w_doto > 0 ? curwp->w_dotp->l_fp : curwp->w_dotp,
			qstr2, NSTRING)
		&& *qstr2 ? qstr2 : NULL;


	/*
	 * Fillregion moves dot to the end of the filled region.
	 */
	if(!fillregion(qstr, &addedregion))
	  return(FALSE);

	set_last_region_added(&addedregion);
    }
    else if(action == 'P'){

	/*
	 * Justfiy the current paragraph.
	 */

	if(curwp->w_markp)		/* clear mark if already set */
	  setmark(0,0);

	if(gotoeop(FALSE, 1) == FALSE)
	  return(FALSE);

	/* determine if we're justifying quoted text or not */
	qstr = quote_match(glo_quote_str, 
			       curwp->w_dotp, qstr2, NSTRING)
		&& *qstr2 ? qstr2 : NULL;

	setmark(0,0);			/* mark last line of para */

	/* jump back to the beginning of the paragraph */
	gotobop(FALSE, 1);

	/* let yank() know that it may be restoring a paragraph */
	thisflag |= (CFFILL | CFFLPA);

	if(!Pmaster)
	  sgarbk = TRUE;
	
	curwp->w_flag |= WFMODE;

	curwp->w_doto = 0;		/* start region at beginning of line */

	/*
	 * Fillregion moves dot to the end of the filled region.
	 */
	if(!fillregion(qstr, &addedregion))
	  return(FALSE);

	set_last_region_added(&addedregion);

	/* Leave cursor on first char of first line after justified region */
	curwp->w_dotp = lforw(curwp->w_dotp);
	curwp->w_doto = 0;

	if(curwp->w_markp)
	  setmark(0,0);			/* clear mark */
    }
    else if(action == 'Q'){
	/* let yank() know that it may be restoring a paragraph */
	thisflag |= CFFILL;

	if(!Pmaster)
	  sgarbk = TRUE;
	
	curwp->w_flag |= WFHARD;

	swap_mark_and_dot_if_mark_comes_first();

	if(!setquotelevelinregion(quotelevel, &addedregion))
	  return(FALSE);

	set_last_region_added(&addedregion);
    }
    else{
	/* abort */
    }

    return(TRUE);
}


/*
 * The region we're filling is the region from dot to mark.
 * We cut out that region and then put it back in filled.
 * The cut out part is saved in the ldelete call and the
 * reinstalled region is noted in addedregion, so that yank()
 * can delete it and restore the saved part.
 */
int
fillregion(UCS *qstr, REGION *addedregion)
{
    long    c, sz, last_char = 0;
    int	    i, j, qlen, same_word,
	    spaces, word_len, word_ind, line_len, ww;
    int     starts_midline = 0;
    int     ends_midline = 0;
    int     offset_into_start;
    LINE   *line_before_start, *lp;
    UCS     line_last, word[NSTRING];
    REGION  region;

    /* if region starts midline insert a newline */
    if(curwp->w_doto > 0 && curwp->w_doto < llength(curwp->w_dotp))
      starts_midline++;

    /* if region ends midline insert a newline at end */
    if(curwp->w_marko > 0 && curwp->w_marko < llength(curwp->w_markp))
      ends_midline++;

    /* cut the paragraph into our fill buffer */
    fdelete();
    if(!getregion(&region, curwp->w_markp, curwp->w_marko))
      return(FALSE);

    if(!ldelete(region.r_size, finsert))
      return(FALSE);

    line_before_start = lback(curwp->w_dotp);
    offset_into_start = curwp->w_doto;

    if(starts_midline)
      lnewline();

    /* Now insert it back wrapped */
    spaces = word_len = word_ind = line_len = same_word = 0;
    qlen = qstr ? ucs4_strlen(qstr) : 0;

    /* Beginning with leading quoting... */
    if(qstr){
	i = 0;
	while(qstr[i]){
	  ww = wcellwidth(qstr[i]);
	  line_len += (ww >= 0 ? ww : 1);
	  linsert(1, qstr[i++]);
	}

	line_last = ' ';			/* no word-flush space! */
    }

    /* remove first leading quotes if any */
    if(starts_midline)
      i = 0;
    else
      for(i = qlen; (c = fremove(i)) == ' ' || c == TAB; i++){
	  linsert(1, line_last = (UCS) c);
	  line_len += ((c == TAB) ? (~line_len & 0x07) + 1 : 1);
      }

    /* then digest the rest... */
    while((c = fremove(i++)) >= 0){
	last_char = c;
	switch(c){
	  case '\n' :
	    /* skip next quote string */
	    j = 0;
	    while(j < qlen && ((c = fremove(i+j)) == qstr[j] || c == ' '))
	      j++;

	    i += j;


	    if(!spaces)
	      spaces++;
	    break;

	  case TAB :
	  case ' ' :
	    spaces++;
	    break;

	  default :
	    if(spaces){				/* flush word? */
		if((line_len - qlen > 0)
		   && line_len + word_len + 1 > fillcol
		   && ((ucs4_isspace(line_last))
		       || (linsert(1, ' ')))
		   && same_word == 0
		   && (line_len = fpnewline(qstr)))
		  line_last = ' ';	/* no word-flush space! */

		if(word_len){			/* word to write? */
		    if(line_len && !ucs4_isspace(line_last)){
			linsert(1, ' ');	/* need padding? */
			line_len++;
		    }

		    line_len += word_len;
		    for(j = 0; j < word_ind; j++)
		      linsert(1, line_last = word[j]);

		    if(spaces > 1 && strchr(".?!:;\")", line_last)){
			linsert(2, line_last = ' ');
			line_len += 2;
		    }

		    same_word = word_len = word_ind = 0;
		}

		spaces = 0;
	    }

	    if(word_ind + 1 >= NSTRING){
		/* Magic!  Fake that we output a wrapped word */
		if((line_len - qlen > 0) && same_word == 0){
		    if(!ucs4_isspace(line_last))
		      linsert(1, ' ');
		    line_len = fpnewline(qstr);
		}
		same_word = 1;
		line_len += word_len;
		for(j = 0; j < word_ind; j++)
		  linsert(1, word[j]);

		word_len = word_ind = 0;
		line_last = ' ';
	    }

	    word[word_ind++] = (UCS) c;
	    ww = wcellwidth((UCS) c);
	    word_len += (ww >= 0 ? ww : 1);

	    break;
	}
    }

    if(word_len){
	if((line_len - qlen > 0) && (line_len + word_len + 1 > fillcol) && same_word == 0){
	    if(!ucs4_isspace(line_last))
	      linsert(1, ' ');
	    (void) fpnewline(qstr);
	}
	else if(line_len && !ucs4_isspace(line_last))
	  linsert(1, ' ');

	for(j = 0; j < word_ind; j++)
	  linsert(1, word[j]);
    }

    if(last_char == '\n')
      lnewline();

    if(ends_midline)
      (void) fpnewline(qstr);

    /*
     * Calculate the size of the region that was added.
     */
    swapmark(0,1);	/* mark current location after adds */
    addedregion->r_linep = lforw(line_before_start);
    addedregion->r_offset = offset_into_start;
    lp = addedregion->r_linep;
    sz = llength(lp) - addedregion->r_offset;
    if(lforw(lp) != curwp->w_markp->l_fp){
	lp = lforw(lp);
	while(lp != curwp->w_markp->l_fp){
	    sz += llength(lp) + 1;
	    lp = lforw(lp);
	}
    }

    sz -= llength(curwp->w_markp) - curwp->w_marko;
    addedregion->r_size = sz;

    swapmark(0,1);

    if(ends_midline){
	/*
	 * We want to back up to the end of the original
	 * region instead of being here after the added newline.
	 */
	curwp->w_doto = 0;
	backchar(0, 1);
	unmarkbuffer();
	markregion(1);
    }

    return(TRUE);
}


/*
 * fpnewline - output a fill paragraph newline mindful of quote string
 */
int
fpnewline(UCS *quote)
{
    int len;

    lnewline();
    for(len = 0; quote && *quote; quote++){
	int ww;

	ww = wcellwidth(*quote);
	len += (ww >= 0 ? ww : 1);
	linsert(1, *quote);
    }

    return(len);
}


int
setquotelevelinregion(int quotelevel, REGION *addedregion)
{
    int     i, standards_based = 0;
    int     quote_chars = 0, backuptoprevline = 0;
    int     starts_midline = 0, ends_midline = 0, offset_into_start;
    long    c, sz;
    UCS     qstr_def1[] = { '>', ' ', 0}, qstr_def2[] = { '>', 0};
    LINE   *lp, *line_before_start;
    REGION  region;

    if(curbp->b_mode&MDVIEW)		/* don't allow this command if	*/
      return(rdonly());			/* we are in read only mode	*/

    if(!glo_quote_str
       || !ucs4_strcmp(glo_quote_str, qstr_def1)
       || !ucs4_strcmp(glo_quote_str, qstr_def2))
      standards_based++;

    if(!standards_based){
	emlwrite("Quote level setting only works with standard \"> \" quotes", NULL);
	return(FALSE);
    }

    /* if region starts midline insert a newline */
    if(curwp->w_doto > 0 && curwp->w_doto < llength(curwp->w_dotp))
      starts_midline++;

    /* if region ends midline insert a newline at end */
    if(curwp->w_marko > 0 && curwp->w_marko < llength(curwp->w_markp)){
	ends_midline++;
	backuptoprevline++;
	/* count quote chars for re-insertion */
	for(i = 0; i < llength(curwp->w_markp); ++i)
	  if(lgetc(curwp->w_markp, i).c != '>')
	    break;

	quote_chars = i;
    }
    else if(curwp->w_marko == 0)
      backuptoprevline++;

    /* find the size of the region */
    getregion(&region, curwp->w_markp, curwp->w_marko);

    /* cut the paragraph into our fill buffer */
    fdelete();
    if(!ldelete(region.r_size, finsert))
      return(FALSE);

    line_before_start = lback(curwp->w_dotp);
    offset_into_start = curwp->w_doto;

    /* if region starts midline add a newline */
    if(starts_midline)
      lnewline();

    i = 0;
    while(fremove(i) >= 0){

	/* remove all quote strs from current line */
	if(standards_based){
	    while((c = fremove(i)) == '>')
	      i++;

	    if(c == ' ')
	      i++;
	}
	else{
	}

	/* insert quotelevel quote strs */
	if(standards_based){
            linsert(quotelevel, '>');
	    if(quotelevel > 0)
              linsert(1, ' ');
	}
	else{
	}

	/* put back the actual line */
	while((c = fremove(i++)) >= 0 && c != '\n')
	  linsert(1, (UCS) c);

	if(c == '\n')
	  lnewline();
    }

    /* if region ends midline add a newline */
    if(ends_midline){
	lnewline();
	if(quote_chars){
	    linsert(quote_chars, '>');
	    if(curwp->w_doto < llength(curwp->w_dotp)
	       && lgetc(curwp->w_dotp, curwp->w_doto).c != ' ')
	      linsert(1, ' ');
	}
    }

    /*
     * Calculate the size of the region that was added.
     */
    swapmark(0,1);	/* mark current location after adds */
    addedregion->r_linep = lforw(line_before_start);
    addedregion->r_offset = offset_into_start;
    lp = addedregion->r_linep;
    sz = llength(lp) - addedregion->r_offset;
    if(lforw(lp) != curwp->w_markp->l_fp){
	lp = lforw(lp);
	while(lp != curwp->w_markp->l_fp){
	    sz += llength(lp) + 1;
	    lp = lforw(lp);
	}
    }

    sz -= llength(curwp->w_markp) - curwp->w_marko;
    addedregion->r_size = sz;

    swapmark(0,1);

    /*
     * This puts us at the end of the quoted region instead
     * of on the following line. This makes it convenient
     * for the user to follow a quotelevel adjustment with
     * a Justify if desired.
     */
    if(backuptoprevline){
	curwp->w_doto = 0;
	backchar(0, 1);
    }

    if(ends_midline){	/* doesn't need fixing otherwise */
	unmarkbuffer();
	markregion(1);
    }

    return (TRUE);
}
