Blob Blame History Raw
diff -rc alpine-2.26/alpine/mailview.c alpine-2.26.fillpara/alpine/mailview.c
*** alpine-2.26/alpine/mailview.c	2022-06-02 18:14:00.463274817 -0600
--- alpine-2.26.fillpara/alpine/mailview.c	2022-06-02 18:14:56.983136230 -0600
***************
*** 203,209 ****
--- 203,217 ----
  int	    pcpine_view_cursor(int, long);
  #endif
  
+ static char *prefix;
+ #define NO_FLOWED  0x0000
+ #define IS_FLOWED  0x0001
+ #define DELETEQUO  0x0010
+ #define COLORAQUO  0x0100
+ #define RAWSTRING  0x1000
  
+ int         is_word (char *, int, int);
+ int         is_mailbox(char *, int, int);
  
  /*----------------------------------------------------------------------
       Format a buffer with the text of the current message for browser
***************
*** 294,299 ****
--- 302,318 ----
  	else
  	  ps->unseen_in_view = !mc->seen;
  
+ 	prefix = reply_quote_str(env);
+ 	/* Make sure the prefix is not only made of spaces, so that we do not
+ 	 * paint the screen incorrectly
+ 	 */
+ 	if (prefix && *prefix){
+ 	   int i;
+ 	   for (i = 0; isspace((unsigned char) prefix[i]); i++);
+ 	   if (i == strlen(prefix))
+ 	     fs_give((void **)&prefix);
+ 	}
+ 
  	init_handles(&handles);
  
  	store = so_get(src, NULL, EDIT_ACCESS);
***************
*** 478,483 ****
--- 497,504 ----
      }
      while(ps->next_screen == SCREEN_FUN_NULL);
  
+     if (prefix && *prefix)
+        fs_give((void **)&prefix);
      if(we_cancel)
        cancel_busy_cue(-1);
  
diff -rc alpine-2.26/pico/basic.c alpine-2.26.fillpara/pico/basic.c
*** alpine-2.26/pico/basic.c	2022-06-02 18:14:00.487274758 -0600
--- alpine-2.26.fillpara/pico/basic.c	2022-06-02 18:14:56.983136230 -0600
***************
*** 22,30 ****
   * framing, are hard.
   */
  #include        "headers.h"
! 
  #include "osdep/terminal.h"
  
  
  /*
   * Move the cursor to the
--- 22,31 ----
   * framing, are hard.
   */
  #include        "headers.h"
! #include	"../pith/osdep/color.h"
  #include "osdep/terminal.h"
  
+ int	indent_match(char **, LINE *, char *, int, int);
  
  /*
   * Move the cursor to the
***************
*** 281,287 ****
  gotobop(int f, int n)
  {
      int quoted, qlen;
!     UCS qstr[NLINE], qstr2[NLINE];
  
      if (n < 0)	/* the other way...*/
        return(gotoeop(f, -n));
--- 282,288 ----
  gotobop(int f, int n)
  {
      int quoted, qlen;
!     char qstr[NLINE], qstr2[NLINE], ind_str[NLINE], pqstr[NLINE];;
  
      if (n < 0)	/* the other way...*/
        return(gotoeop(f, -n));
***************
*** 293,298 ****
--- 294,307 ----
  	    curwp->w_dotp = lback(curwp->w_dotp);
  	    curwp->w_doto = 0;
  	}
+ 
+ 	if (indent_match(default_qstr(glo_quote_str, 1), curwp->w_dotp,ind_str, NLINE, 0)){
+ 	   if (n){ /* look for another paragraph ? */
+ 	      curwp->w_dotp = lback(curwp->w_dotp);
+ 	      continue;
+ 	   }
+ 	   break;
+ 	}
  	
  	/* scan line by line until we come to a line ending with
  	 * a <NL><NL> or <NL><TAB> or <NL><SPACE>
***************
*** 300,317 ****
  	 * PLUS: if there's a quote string, a quoted-to-non-quoted
  	 *	 line transition.
  	 */
! 	quoted = quote_match(glo_quote_str, curwp->w_dotp, qstr, NLINE);
! 	qlen   = quoted ? ucs4_strlen(qstr) : 0;
  	while(lback(curwp->w_dotp) != curbp->b_linep
  	      && llength(lback(curwp->w_dotp)) > qlen
! 	      && quoted == quote_match(glo_quote_str,
  					   lback(curwp->w_dotp),
! 					   qstr2, NLINE)
! 	      && !ucs4_strcmp(qstr, qstr2)
! 	      && lgetc(curwp->w_dotp, qlen).c != TAB
! 	      && lgetc(curwp->w_dotp, qlen).c != ' ')
  	  curwp->w_dotp = lback(curwp->w_dotp);
  
  	if(n){
  	    /* keep looking */
  	    if(lback(curwp->w_dotp) == curbp->b_linep)
--- 309,367 ----
  	 * PLUS: if there's a quote string, a quoted-to-non-quoted
  	 *	 line transition.
  	 */
! 	quoted = quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, 0);
! 	qlen   = quoted ? strlen(qstr) : 0;
  	while(lback(curwp->w_dotp) != curbp->b_linep
  	      && llength(lback(curwp->w_dotp)) > qlen
! 	      && (quoted == quote_match(default_qstr(glo_quote_str, 1),
  					   lback(curwp->w_dotp),
! 					   qstr2, NLINE, 0))
! 	      && !strcmp(qstr, qstr2)  /* processed string */
! 	      && (quoted == quote_match(default_qstr(glo_quote_str, 1),
! 			lback(curwp->w_dotp), qstr2, NLINE, 1))
! 	      && !strcmp(qstr, qstr2)   /* raw string */
! 	      && !indent_match(default_qstr(glo_quote_str, 1),
! 			lback(curwp->w_dotp),ind_str, NLINE, 0)
! 	      && !ISspace(lgetc(curwp->w_dotp, qlen).c))
  	  curwp->w_dotp = lback(curwp->w_dotp);
  
+ 	 /*
+ 	  * Ok, we made it here and we assume that we are at the begining
+ 	  * of the paragraph. Let's double check this now. In order to do
+ 	  * so we shall check if the first line was indented in a special
+ 	  * way.
+ 	  */
+ 	if(lback(curwp->w_dotp) == curbp->b_linep)
+ 	    break;
+ 	else{
+ 	     int i, j;
+ 
+ 	   /*
+ 	    * First we test if the preceding line is indented.
+ 	    * for the following test we need to have the raw values,
+ 	    * not the processed values
+ 	    */
+ 	   quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, 1);
+ 	   quote_match(default_qstr(glo_quote_str, 1), lback(curwp->w_dotp), qstr2, NLINE, 1);
+ 	   for (i = 0, j = 0;
+ 	        qstr[i] && qstr2[i] && (qstr[i] == qstr2[i]); i++, j++);
+ 	   for (; ISspace(qstr2[i]); i++);
+ 	   for (; ISspace(qstr[j]); j++);
+ 	   if ((indent_match(default_qstr(glo_quote_str, 1), lback(curwp->w_dotp),
+ 						ind_str, NLINE, 1)
+ 	       && (strlenis(qstr2) 
+ 			+ strlenis(ind_str) >= strlenis(qstr)))
+ 	      || ((lback(curwp->w_dotp) != curbp->b_linep
+ 	         && llength(lback(curwp->w_dotp)) > qlen
+ 	         && (quoted == quote_match(default_qstr(glo_quote_str, 1),
+ 				lback(curwp->w_dotp), pqstr, NLINE, 0))
+ 		 && !strcmp(qstr, pqstr)
+ 		 && !ISspace(lgetc(curwp->w_dotp, qlen).c)
+ 		 && strlenis(qstr2) > strlenis(qstr))
+ 	         && !qstr2[i] && !qstr[j]))
+ 		curwp->w_dotp = lback(curwp->w_dotp);
+ 	}
+ 
  	if(n){
  	    /* keep looking */
  	    if(lback(curwp->w_dotp) == curbp->b_linep)
***************
*** 324,330 ****
  	else{
  	  /* leave cursor on first word in para */
  	    curwp->w_doto = 0;
! 	    while(ucs4_isspace(lgetc(curwp->w_dotp, curwp->w_doto).c))
  	      if(++curwp->w_doto >= llength(curwp->w_dotp)){
  		  curwp->w_doto = 0;
  		  curwp->w_dotp = lforw(curwp->w_dotp);
--- 374,380 ----
  	else{
  	  /* leave cursor on first word in para */
  	    curwp->w_doto = 0;
! 	    while(ISspace(lgetc(curwp->w_dotp, curwp->w_doto).c))
  	      if(++curwp->w_doto >= llength(curwp->w_dotp)){
  		  curwp->w_doto = 0;
  		  curwp->w_dotp = lforw(curwp->w_dotp);
***************
*** 347,354 ****
  int
  gotoeop(int f, int n)
  {
!     int quoted, qlen;
!     UCS qstr[NLINE], qstr2[NLINE];
  
      if (n < 0)	/* the other way...*/
        return(gotobop(f, -n));
--- 397,405 ----
  int
  gotoeop(int f, int n)
  {
!     int quoted, qlen, indented, changeqstr = 0;
!     int i,j, fli = 0; /* fli = first line indented a boolean variable */
!     char qstr[NLINE], qstr2[NLINE], ind_str[NLINE];
  
      if (n < 0)	/* the other way...*/
        return(gotobop(f, -n));
***************
*** 361,384 ****
  	      break;
  	}
  
! 	/* scan line by line until we come to a line ending with
! 	 * a <NL><NL> or <NL><TAB> or <NL><SPACE>
! 	 *
! 	 * PLUS: if there's a quote string, a quoted-to-non-quoted
! 	 *	 line transition.
  	 */
! 	quoted = quote_match(glo_quote_str,
! 			curwp->w_dotp, qstr, NLINE);
! 	qlen   = quoted ? ucs4_strlen(qstr) : 0;
  	
  	while(curwp->w_dotp != curbp->b_linep
  	      && llength(lforw(curwp->w_dotp)) > qlen
! 	      && (quoted == quote_match(glo_quote_str,
! 					   lforw(curwp->w_dotp),
! 					   qstr2, NLINE)
! 		     && !ucs4_strcmp(qstr, qstr2))
! 	      && lgetc(lforw(curwp->w_dotp), qlen).c != TAB
! 	      && lgetc(lforw(curwp->w_dotp), qlen).c != ' ')
  	  curwp->w_dotp = lforw(curwp->w_dotp);
  
  	curwp->w_doto = llength(curwp->w_dotp);
--- 412,481 ----
  	      break;
  	}
  
! 	/*
! 	 * We need to figure out if this line is the first line of
! 	 * a paragraph that has been indented in a special way. If this
! 	 * is the case, we advance one more line before we use the
! 	 * algorithm below
! 	 */
! 
! 	if(curwp->w_dotp != curbp->b_linep){
! 	   quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, 1);
! 	   quote_match(default_qstr(glo_quote_str, 1), lforw(curwp->w_dotp), qstr2, NLINE, 1);
! 	   indented = indent_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, ind_str,
! 							NLINE, 1);
! 	   if (strlenis(qstr) 
! 		+ strlenis(ind_str) < strlenis(qstr2)){
! 		curwp->w_doto = llength(curwp->w_dotp);
! 		if(n){    /* this line is a paragraph by itself */
! 		   curwp->w_dotp = lforw(curwp->w_dotp);
! 		   continue;
! 		}
! 		break;
! 	   }
! 	   for (i=0,j=0; qstr[i] && qstr2[i] && (qstr[i] == qstr2[i]);i++,j++);
! 	   for (; ISspace(qstr[i]); i++);
! 	   for (; ISspace(qstr2[j]); j++);
! 	   if (!qstr[i] && !qstr2[j] && indented){
! 		fli++;
! 		if (indent_match(default_qstr(glo_quote_str, 1), lforw(curwp->w_dotp),
! 					ind_str, NLINE, 0)){
! 		    if (n){ /* look for another paragraph ? */
! 		      curwp->w_dotp = lforw(curwp->w_dotp);
! 		      continue;
! 		    }
! 		}
! 		else{
! 		  if (!lisblank(lforw(curwp->w_dotp)))
! 		     curwp->w_dotp = lforw(curwp->w_dotp);
! 		}
! 	   }
! 	}
! 
!   	/* scan line by line until we come to a line ending with
!   	 * a <NL><NL> or <NL><TAB> or <NL><SPACE>
!   	 *
!   	 * PLUS: if there's a quote string, a quoted-to-non-quoted
!   	 *	 line transition.
!   	 */
! 	/* if the first line is indented (fli == 1), then the test below
! 	   is on the second line, and in that case we will need the raw
! 	   string, not the processed string
  	 */
! 	quoted = quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, fli);
! 	qlen   = quoted ? strlen(qstr) : 0;
  	
  	while(curwp->w_dotp != curbp->b_linep
  	      && llength(lforw(curwp->w_dotp)) > qlen
! 	      && (quoted == quote_match(default_qstr(glo_quote_str, 1),
! 				lforw(curwp->w_dotp), qstr2, NLINE, fli))
! 	      && !strcmp(qstr, qstr2)
! 	      && (quoted == quote_match(default_qstr(glo_quote_str, 1),
! 				lforw(curwp->w_dotp), qstr2, NLINE, 1))
! 	      && !strcmp(qstr, qstr2)
! 	      && !indent_match(default_qstr(glo_quote_str, 1),
! 				lforw(curwp->w_dotp), ind_str, NLINE, 0)
! 	      && !ISspace(lgetc(lforw(curwp->w_dotp), qlen).c))
  	  curwp->w_dotp = lforw(curwp->w_dotp);
  
  	curwp->w_doto = llength(curwp->w_dotp);
diff -rc alpine-2.26/pico/efunc.h alpine-2.26.fillpara/pico/efunc.h
*** alpine-2.26/pico/efunc.h	2022-06-02 18:14:00.487274758 -0600
--- alpine-2.26.fillpara/pico/efunc.h	2022-06-02 18:14:56.983136230 -0600
***************
*** 253,262 ****
  extern	int fillpara(int, int);
  extern	int fillbuf(int, int);
  extern	int inword(void);
! extern	int quote_match(UCS *, LINE *, UCS *, size_t);
  extern	int ucs4_isalnum(UCS);
  extern	int ucs4_isalpha(UCS);
  extern	int ucs4_isspace(UCS);
  extern	int ucs4_ispunct(UCS);
  
  #endif	/* EFUNC_H */
--- 253,268 ----
  extern	int fillpara(int, int);
  extern	int fillbuf(int, int);
  extern	int inword(void);
! extern	int quote_match(char **, LINE *, char *, size_t, int);
! extern	void flatten_qstring(QSTRING_S *, char *, int);
! extern	void free_qs(QSTRING_S **);
! extern	QSTRING_S *do_quote_match (char **, char *, char *, char *, char *, int, int);
! extern	QSTRING_S *do_raw_quote_match(char **, char *, char *, char *, QSTRING_S **, QSTRING_S **);
! extern  int indent_match(char **, LINE *, char *, int, int);
  extern	int ucs4_isalnum(UCS);
  extern	int ucs4_isalpha(UCS);
  extern	int ucs4_isspace(UCS);
  extern	int ucs4_ispunct(UCS);
  
  #endif	/* EFUNC_H */
+ 
diff -rc alpine-2.26/pico/line.c alpine-2.26.fillpara/pico/line.c
*** alpine-2.26/pico/line.c	2022-06-02 18:14:00.487274758 -0600
--- alpine-2.26.fillpara/pico/line.c	2022-06-02 18:14:57.007136171 -0600
***************
*** 608,623 ****
  lisblank(LINE *line)
  {
      int n = 0;
!     UCS qstr[NLINE];
  
!     n = (glo_quote_str
! 	 && quote_match(glo_quote_str, line, qstr, NLINE))
! 	  ? ucs4_strlen(qstr) : 0;
  
      for(; n < llength(line); n++)
!       if(!ucs4_isspace(lgetc(line, n).c) 
! 	  || lgetc(line, n).c >= 0xff 
! 	  || (unsigned char) lgetc(line,n).c != (unsigned char) NBSPC)
  	return(FALSE);
  
      return(TRUE);
--- 608,619 ----
  lisblank(LINE *line)
  {
      int n = 0;
!     char qstr[NLINE];
  
!     n = quote_match(default_qstr(glo_quote_str, 1), line, qstr, NLINE, 1);
  
      for(; n < llength(line); n++)
!       if(!ISspace(lgetc(line, n).c))
  	return(FALSE);
  
      return(TRUE);
diff -rc alpine-2.26/pico/osdep/color.h alpine-2.26.fillpara/pico/osdep/color.h
*** alpine-2.26/pico/osdep/color.h	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pico/osdep/color.h	2022-06-02 18:14:57.007136171 -0600
***************
*** 33,38 ****
--- 33,39 ----
  void	 pico_toggle_color(int);
  void	 pico_set_nfg_color(void);
  void	 pico_set_nbg_color(void);
+ char	 **default_qstr(void *, int);
  
  
  #endif /* PICO_OSDEP_COLOR_INCLUDED */
diff -rc alpine-2.26/pico/search.c alpine-2.26.fillpara/pico/search.c
*** alpine-2.26/pico/search.c	2022-06-02 18:14:00.487274758 -0600
--- alpine-2.26.fillpara/pico/search.c	2022-06-02 18:14:57.015136151 -0600
***************
*** 32,37 ****
--- 32,38 ----
  int     readpattern(char *, int, int);
  int     replace_pat(UCS *, int *, int);
  int     replace_all(UCS *, UCS *, int);
+ int	deletepara(int, int);
  void	reverse_line(LINE *);
  void	reverse_buffer(void);
  void	reverse_ucs4(UCS *);
***************
*** 67,73 ****
  	{"^W", N_("Start of Para"), (CTRL|'W')},
  	{"^O", N_("End of Para"),   (CTRL|'O')},
  	{"^U", N_("FullJustify"),   (CTRL|'U')},
! 	{NULL, NULL, 0},
  	{NULL, NULL, 0}
  };
  
--- 68,74 ----
  	{"^W", N_("Start of Para"), (CTRL|'W')},
  	{"^O", N_("End of Para"),   (CTRL|'O')},
  	{"^U", N_("FullJustify"),   (CTRL|'U')},
! 	{"^P", N_("Delete Para"),   (CTRL|'P')},
  	{NULL, NULL, 0}
  };
  
***************
*** 216,224 ****
  
  	  case (CTRL|'P'):
  	    if(flags & SR_ORIGMEN){
! 		/* Undefined still */
  	    }
! 	    if(flags & SR_OPTNMEN){
  	      if(flags & SR_FORWARD){
  		flags &= ~SR_FORWARD;
  		flags |=  SR_BACKWRD;
--- 217,227 ----
  
  	  case (CTRL|'P'):
  	    if(flags & SR_ORIGMEN){
! 	      deletepara(0, 1);
! 	      mlerase();
! 	      FWS_RETURN(TRUE);
  	    }
! 	    else if(flags & SR_OPTNMEN){
  	      if(flags & SR_FORWARD){
  		flags &= ~SR_FORWARD;
  		flags |=  SR_BACKWRD;
***************
*** 1416,1418 ****
--- 1419,1443 ----
    if(bsearch) reverse_ucs4(orig);
    return utf8;
  }
+ 
+ int
+ deletepara(int f, int n) /* Delete the current paragraph */
+ {
+    if(curbp->b_mode&MDVIEW)           /* don't allow this command if  */ 
+      return(rdonly());               /* we are in read only mode     */
+    
+    if(!lisblank(curwp->w_dotp))
+      gotobop(FALSE, 1);
+ 
+    curwp->w_markp = curwp->w_dotp;
+    curwp->w_marko = 0;
+ 
+    gotoeop(FALSE, 1);
+    if (curwp->w_dotp != curbp->b_linep){ /* if we are not at the end of buffer */
+      curwp->w_dotp = lforw(curwp->w_dotp); /* get one more line */
+        curwp->w_doto = 0; /* but only the beginning */
+    }
+    killregion(f,n);
+    return(TRUE);
+ }
+ 
diff -rc alpine-2.26/pico/word.c alpine-2.26.fillpara/pico/word.c
*** alpine-2.26/pico/word.c	2022-06-02 18:14:00.487274758 -0600
--- alpine-2.26.fillpara/pico/word.c	2022-06-02 18:14:57.019136141 -0600
***************
*** 21,30 ****
   */
  
  #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);
  
--- 21,30 ----
   */
  
  #include	"headers.h"
! #include	"../pith/osdep/color.h"
  
  int fpnewline(UCS *quote);
! int fillregion(UCS *qstr, UCS *istr, REGION *addedregion);
  int setquotelevelinregion(int quotelevel, REGION *addedregion);
  int is_user_separator(UCS c);
  
***************
*** 427,551 ****
      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
--- 427,591 ----
      return 0;
  }
  
! /* Support of indentation of paragraphs */
! #define is_indent_char(c)  (((c) == '.' || (c) == '}' || (c) == RPAREN || \
! 			     (c) == '*' || (c) == '+' || is_a_digit(c) || \
! 			     ISspace(c) || (c) == '-' || \
! 			     (c) == ']') ? 1 : 0)
! #define allowed_after_digit(c,word,k)  ((((c) == '.' && \
! 			     allowed_after_period(next((word),(k))))  ||\
! 				(c) == RPAREN || (c) == '}' || (c) == ']' ||\
! 				  ISspace(c) ||  is_a_digit(c) || \
! 				  ((c) == '-' ) && \
! 				    allowed_after_dash(next((word),(k)))) \
! 				? 1 : 0)
! #define allowed_after_period(c)	 (((c) == RPAREN || (c) == '}' || (c) == ']' ||\
! 				   ISspace(c) || (c) == '-' || \
! 				   is_a_digit(c)) ? 1 : 0)
! #define allowed_after_parenth(c)  (ISspace(c) ? 1 : 0)
! #define allowed_after_space(c)	  (ISspace(c) ? 1 : 0)
! #define allowed_after_braces(c)	  (ISspace(c) ? 1 : 0)
! #define allowed_after_star(c)	 ((ISspace(c) || (c) == RPAREN ||\
!                                        (c) == ']' || (c) == '}') ? 1 : 0)
! #define allowed_after_dash(c)	  ((ISspace(c) || is_a_digit(c)) ? 1 : 0)
! #define EOLchar(c)		  (((c) == '.' || (c) == ':' || (c) == '?' ||\
! 					(c) == '!') ? 1 : 0)
! 
! int indent_match(char **, LINE *, char *, int, int);
! 
! /* Extended justification support */
! #define is_cquote(c) ((c) == '>' || (c) == '|' || (c) == ']' || (c) == ':')
! #define is_cword(c)  ((((c) >= 'a') && ((c) <= 'z')) ||  \
!                      (((c) >= 'A') && ((c) <= 'Z')) || \
!                      (((c) >= '0') && ((c) <= '9')) || \
!                       ((c) == ' ') || ((c) == '?') || \
!                       ((c) == '@') || ((c) == '.') || \
!                       ((c) == '!') || ((c) == '\'') || \
!                       ((c) == ',') || ((c) == '\"') ? 1 : 0)
! #define isaquote(c)   ((c) == '\"' || (c) == '\'')
! #define is8bit(c)     ((((int) (c)) & 0x80) ? 1 : 0)
! #define iscontrol(c)  (iscntrl(((int) (c)) & 0x7f) ? 1 : 0)
! #define forbidden(c)  (((c) == '\"') || ((c) == '\'') || ((c) == '$') ||\
!                        ((c) == ',')  || ((c) == '.')  || ((c) == '-') ||\
!                        ((c) == LPAREN) || ((c) == '/')|| ((c) == '`') ||\
!                        ((c) == '{') || ((c) == '\\') || (iscontrol((c))) ||\
!                        (((c) >= '0')  && ((c) <= '9')) || ((c) == '?'))
! #define is_cletter(c)  ((((c) >= 'a') && ((c) <= 'z'))) ||\
!                        ((((c) >= 'A') && ((c) <= 'Z'))||\
!                       is8bit(c))
! #define is_cnumber(c) ((c) >= '0' && (c) <= '9')
! #define allwd_after_word(c) (((c) == ' ') || ((c) == '>') || is_cletter(c))
! #define allwd_after_qsword(c)  (((c) != '\\') && ((c) != RPAREN) && ((c) !=  '/'))
! #define before(word,i) (((i) > 0) ? (word)[(i) - 1] : 0)
! #define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0)
! #define now(w,i)  ((w)[(i)])
! #define is_qsword(c)  (((c) == ':') || ((c) == RPAREN) ? 1 : 0)
! #define is_colon(c)   (((c) == ':') ? 1 : 0)
! #define is_rarrow(c)  (((c) == '>') ? 1 : 0)
! #define is_tilde(c)   (((c) == '~') ? 1 : 0)
! #define is_dash(c)    (((c) == '-') ? 1 : 0)
! #define is_pound(c)   (((c) == '#') ? 1 : 0)
! #define is_a_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0)
! #define is_allowed(c)  (is_cquote(c) || is_cword(c) || is_dash(c) || \
!                        is_pound(c))
! #define qs_allowed(a)  (((a)->qstype != qsGdb) && ((a)->qstype != qsProg))
! 
! /* Internal justification functions */
! QSTRING_S *qs_quote_match(char **, LINE *, char *, int);
! int	ucs4_widthis(UCS *);
! int     ucs4_strlenis(UCS *);
! void    linencpy(char *, LINE *, int);
  
! void
! linencpy(word, l, buflen)
!  char word[NSTRING];
!  LINE *l;
!  int buflen;
! {
!   int i;
!   UCS ucs_word[NSTRING];
!   char *utf_word;
! 
!   word[0] = '\0';
!   if(l){
!     for (i = 0; i < buflen && i < llength(l)
!                && (ucs_word[i] = lgetc(l,i).c); i++);
!     ucs_word[i == buflen ? i-1 : i] = '\0';
!     utf_word = ucs4_to_utf8_cpystr(ucs_word);
!     strncpy(word, utf_word, (NSTRING < buflen ? NSTRING : buflen));
!     word[NSTRING-1] = '\0';
!     if(utf_word) fs_give((void **)&utf_word);
!   }
! }
! 
!  /*
!   * This function returns the quote string as a structure. In this way we
!   * have two ways to get the quote string: as a char * or as a QSTRING_S *
!   * directly.
!   */
! QSTRING_S *
! qs_quote_match(char **q, LINE *l, char *rqstr, int rqstrlen)
! {
!     char GLine[NSTRING], NLine[NSTRING], PLine[NSTRING];
!     LINE *nl = l != curbp->b_linep ? lforw(l) : NULL;
!     LINE *pl = lback(l) != curbp->b_linep ? lback(l) : NULL;
!     int plb = 1;
! 
!    linencpy(GLine, l, NSTRING);
!    linencpy(NLine, nl, NSTRING);
! 
!    if (pl){
!       linencpy(PLine, pl, NSTRING);
!       if(lback(pl) != curbp->b_linep){
! 	char PPLine[NSTRING];
  
! 	linencpy(PPLine, lback(pl), NSTRING);
! 	plb = line_isblank(q, PLine, GLine, PPLine, NSTRING);
!       }
!    }
!    return do_quote_match(q, GLine, NLine, PLine, rqstr, rqstrlen, plb);
  }
  
  /*
   * Return number of quotes if whatever starts the line matches the quote string
+  * rqstr is a pointer to raw qstring; buf points to processed qstring
   */
  int
! quote_match(char **q, LINE *l, char *buf, size_t buflen, int raw)
  {
!     QSTRING_S *qs;
!     char rqstr[NSTRING];
  
!     qs = qs_quote_match(q, l, rqstr, NSTRING);
!     if(qs)
! 	record_quote_string(qs);
!     flatten_qstring(qs, buf, buflen);
!     if (qs) free_qs(&qs);
! 
!     if(raw){
!       strncpy(buf, rqstr, buflen < NSTRING ? buflen : NSTRING);
!       buf[buflen-1] = '\0';
!     }
! 
!     return  buf && buf[0] ? strlen(buf) : 0;
  }
  
+ int ucs4_widthis(UCS *ucstr)
+ {
+   int i, rv = 0;
+   for (i = 0; ucstr && ucstr[i]; i++)
+        rv += ((ucstr[i] == TAB) ? (~rv & 0x07) + 1 : wcellwidth((UCS) ucstr[i]));
+   return rv;
+ }
+ 
+ int ucs4_strlenis(UCS *ucs_qstr)
+ {
+   char *str = ucs4_to_utf8_cpystr(ucs_qstr);
+   int i = (int) strlenis(str);
+ 
+   if(str) fs_give((void **)&str);
+   return i;
+ }
  
  /* Justify the entire buffer instead of just a paragraph */
  int
***************
*** 800,805 ****
--- 840,846 ----
      }
  
      if(action == 'R' && curwp->w_markp){
+ 	char qstrfl[NSTRING];
  	/* let yank() know that it may be restoring a paragraph */
  	thisflag |= CFFILL;
  
***************
*** 811,831 ****
  	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.
--- 852,877 ----
  	swap_mark_and_dot_if_mark_comes_first();
  
  	/* determine if we're justifying quoted text or not */
! 	qstr = (glo_quote_str
! 		&& quote_match(default_qstr(glo_quote_str, 1), 
! 			       (curwp->w_doto > 0 ? curwp->w_dotp->l_fp : curwp->w_dotp),
! 			       qstrfl, NSTRING, 0)
! 		&& *qstrfl) ? utf8_to_ucs4_cpystr(qstrfl) : NULL;
  
  	/*
  	 * Fillregion moves dot to the end of the filled region.
  	 */
! 	if(!fillregion(qstr, NULL, &addedregion))
  	  return(FALSE);
  
  	set_last_region_added(&addedregion);
! 
! 	if(qstr)
! 	  fs_give((void **)&qstr);
!       }
!       else if(action == 'P'){
! 	char ind_str[NSTRING], qstrfl[NSTRING];
! 	UCS *istr;
  
  	/*
  	 * Justfiy the current paragraph.
***************
*** 837,852 ****
  	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);
  
--- 883,898 ----
  	if(gotoeop(FALSE, 1) == FALSE)
  	  return(FALSE);
  
  	setmark(0,0);			/* mark last line of para */
  
  	/* jump back to the beginning of the paragraph */
  	gotobop(FALSE, 1);
  
+ 	istr = indent_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, ind_str, NSTRING, 0)
+ 	   && *ind_str  ? utf8_to_ucs4_cpystr(ind_str) : NULL;
+ 	qstr = (quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstrfl, NSTRING, 0)
+             && *qstrfl) ? utf8_to_ucs4_cpystr(qstrfl) : NULL;
+ 
  	/* let yank() know that it may be restoring a paragraph */
  	thisflag |= (CFFILL | CFFLPA);
  
***************
*** 860,868 ****
  	/*
  	 * 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 */
--- 906,920 ----
  	/*
  	 * Fillregion moves dot to the end of the filled region.
  	 */
! 	if(!fillregion(qstr, istr, &addedregion))
  	  return(FALSE);
  
+ 	if(qstr)
+ 	  fs_give((void **)&qstr);
+ 
+ 	if(istr)
+ 	  fs_give((void **)&istr);
+ 
  	set_last_region_added(&addedregion);
  
  	/* Leave cursor on first char of first line after justified region */
***************
*** 904,919 ****
   * 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 */
--- 956,971 ----
   * can delete it and restore the saved part.
   */
  int
! fillregion(UCS *qstr, UCS *istr, REGION *addedregion)
  {
      long    c, sz, last_char = 0;
!     int	    i, j, qlen, same_word, qi, pqi, qlenis,
  	    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], quoid[NSTRING], qstr2[NSTRING];
      REGION  region;
  
      /* if region starts midline insert a newline */
***************
*** 924,929 ****
--- 976,1010 ----
      if(curwp->w_marko > 0 && curwp->w_marko < llength(curwp->w_markp))
        ends_midline++;
  
+     for (i = 0; (i < NSTRING) && qstr && (quoid[i] = qstr[i]); i++);
+     for (j = 0; ((i + j) < NSTRING) && istr && (quoid[i] = istr[j]); i++,j++);
+     quoid[i] = '\0';
+     qi = ucs4_strlen(quoid);
+     if (istr)			/* strip trailing spaces */
+        for (;ISspace(quoid[qi - 1]); qi--);
+     quoid[qi] = '\0';     /* we have closed quoid at "X" in the first line */
+ 
+     if (ucs4_strlenis(quoid) > fillcol)
+ 	return FALSE;		/* Too wide, we can't justify this! */
+ 
+     if (qstr && istr){
+ 	for (i = ucs4_strlen(qstr) - 1; ISspace(qstr[i]); i--);
+ 	qstr[i + 1] = '\0';	/* qstrfl */
+     }
+     qlen   = ucs4_strlen(qstr);	/* qstrfl*/
+     qlenis = ucs4_strlenis(qstr);
+ 
+     for(i = 0, qstr2[0] = '\0'; qstr && qstr[i] && (qstr2[i] = qstr[i]); i++);
+ 
+     if (istr && ((j = ucs4_widthis(quoid) - ucs4_widthis(qstr)) > 0)){
+ 	pqi = ucs4_strlen(qstr);
+ 	for (i = 0; (i < j) && (qstr2[pqi + i] = ' '); i++);
+ 	if (ISspace(istr[ucs4_strlen(istr) - 1]))
+ 	   qstr2[pqi + i++] = ' ';
+ 	qstr2[pqi + i] = '\0';
+ 	qstr = qstr2;
+     }
+ 
      /* cut the paragraph into our fill buffer */
      fdelete();
      if(!getregion(&region, curwp->w_markp, curwp->w_marko))
***************
*** 940,967 ****
  
      /* 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){
--- 1021,1056 ----
  
      /* Now insert it back wrapped */
      spaces = word_len = word_ind = line_len = same_word = 0;
  
      /* Beginning with leading quoting... */
!     if(qstr || istr){
! 	for(i = 0; quoid[i] != '\0' ; i++)
! 	  linsert(1, quoid[i]);
  
  	line_last = ' ';			/* no word-flush space! */
+         line_len = ucs4_strlenis(quoid);         /* we demand a recount! */
      }
  
      /* remove first leading quotes if any */
      if(starts_midline)
        i = 0;
!     else{
!       if(qstr || istr){
!         for (i = 0; (c = fremove(i)) != '\0'; i++){
!                 word[i] = c;
!                 word[i+1] = '\0';
!                 if(ucs4_strlenis(word) >= ucs4_strlenis(quoid))
!                 break;
!         }
! 	i++;
!       }
!       else
! 	i = 0;
!       for(; ISspace(c = fremove(i)); 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){
***************
*** 982,1002 ****
  
  	  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++;
  		    }
--- 1071,1092 ----
  
  	  case TAB :
  	  case ' ' :
+ 	  case NBSP:
  	    spaces++;
  	    break;
  
  	  default :
  	    if(spaces){				/* flush word? */
! 		if((line_len - qlenis > 0)
  		   && line_len + word_len + 1 > fillcol
! 		   && ((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 && !ISspace(line_last)){
  			linsert(1, ' ');	/* need padding? */
  			line_len++;
  		    }
***************
*** 1018,1025 ****
  
  	    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);
  		}
--- 1108,1115 ----
  
  	    if(word_ind + 1 >= NSTRING){
  		/* Magic!  Fake that we output a wrapped word */
! 		if((line_len - qlenis > 0) && same_word == 0){
! 		    if(!ISspace(line_last))
  		      linsert(1, ' ');
  		    line_len = fpnewline(qstr);
  		}
***************
*** 1041,1052 ****
      }
  
      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++)
--- 1131,1142 ----
      }
  
      if(word_len){
! 	if((line_len - qlenis > 0) && (line_len + word_len + 1 > fillcol) && same_word == 0){
! 	    if(!ISspace(line_last))
  	      linsert(1, ' ');
  	    (void) fpnewline(qstr);
  	}
! 	else if(line_len && !ISspace(line_last))
  	  linsert(1, ' ');
  
  	for(j = 0; j < word_ind; j++)
***************
*** 1104,1114 ****
      int len;
  
      lnewline();
!     for(len = 0; quote && *quote; quote++){
  	int ww;
  
! 	ww = wcellwidth(*quote);
! 	len += (ww >= 0 ? ww : 1);
  	linsert(1, *quote);
      }
  
--- 1194,1204 ----
      int len;
  
      lnewline();
!     for(len = ucs4_strlenis(quote); quote && *quote; quote++){
  	int ww;
  
! /*	ww = wcellwidth(*quote);
! 	len += (ww >= 0 ? ww : 1);*/
  	linsert(1, *quote);
      }
  
***************
*** 1252,1256 ****
--- 1342,1386 ----
  	markregion(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);
  }
+ 
+ /*
+  * If there is an indent string this function returns
+  * its length
+  */ 
+ int
+ indent_match(char **q, LINE *l, char *buf, int buflen, int raw)
+ {
+      char GLine[NSTRING];
+      int  i, k, plb;
+        
+      k = quote_match(q,l, buf, buflen, raw);
+      linencpy(GLine, l, NSTRING);
+      plb = (lback(l) != curbp->b_linep) ? lisblank(lback(l)) : 1;
+      if (!plb){
+         i = llength(lback(l)) - 1;
+         for (; i >= 0 && ISspace(lgetc(lback(l), i).c); i--);
+         if (EOLchar(lgetc(lback(l), i).c))
+           plb++;
+      }      
+     
+      return get_indent_raw_line(q, GLine, buf, buflen, k, plb);
+ }
+ 
diff -rc alpine-2.26/pith/charconv/utf8.c alpine-2.26.fillpara/pith/charconv/utf8.c
*** alpine-2.26/pith/charconv/utf8.c	2022-06-02 18:14:00.495274738 -0600
--- alpine-2.26.fillpara/pith/charconv/utf8.c	2022-06-02 18:14:57.019136141 -0600
***************
*** 1113,1118 ****
--- 1113,1168 ----
  
  
  /*
+  * Returns the screen cells width of the UTF-8 string argument, treating tabs
+  * in a special way.
+  */
+ unsigned
+ utf8_widthis(char *str)
+ {
+     unsigned width = 0;
+     int this_width;
+     UCS ucs;
+     unsigned long remaining_octets;
+     char *readptr;
+ 
+     if(!(str && *str))
+       return(width);
+ 
+     readptr = str;
+     remaining_octets = readptr ? strlen(readptr) : 0;
+ 
+     while(remaining_octets > 0 && *readptr){
+ 
+ 	ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets);
+ 
+ 	if(ucs & U8G_ERROR){
+ 	    /*
+ 	     * This should not happen, but do something to handle it anyway.
+ 	     * Treat each character as a single width character, which is what should
+ 	     * probably happen when we actually go to write it out.
+ 	     */
+ 	    remaining_octets--;
+ 	    readptr++;
+ 	    this_width = 1;
+ 	}
+ 	else{
+ 	    this_width = (ucs == TAB) ? ((~width & 0x07) + 1) : wcellwidth(ucs);
+ 
+ 	    /*
+ 	     * If this_width is -1 that means we can't print this character
+ 	     * with our current locale. Writechar will print a '?'.
+ 	     */
+ 	    if(this_width < 0)
+ 	      this_width = 1;
+ 	}
+ 
+ 	width += (unsigned) this_width;
+     }
+ 
+     return(width);
+ }
+ 
+ /*
   * Copy UTF-8 characters from src into dst.
   * This is intended to be used if you want to truncate a string at
   * the start instead of the end. For example, you have a long string
diff -rc alpine-2.26/pith/charconv/utf8.h alpine-2.26.fillpara/pith/charconv/utf8.h
*** alpine-2.26/pith/charconv/utf8.h	2022-06-02 18:14:00.495274738 -0600
--- alpine-2.26.fillpara/pith/charconv/utf8.h	2022-06-02 18:14:57.023136132 -0600
***************
*** 82,87 ****
--- 82,88 ----
  UCS           *ucs4_strchr(UCS *s, UCS c);
  UCS           *ucs4_strrchr(UCS *s, UCS c);
  unsigned       utf8_width(char *);
+ unsigned       utf8_widthis(char *);
  size_t         utf8_to_width_rhs(char *, char *, size_t, unsigned);
  int            utf8_snprintf(char *, size_t, char *, ...);
  size_t         utf8_to_width(char *, char *, size_t, unsigned, unsigned *);
diff -rc alpine-2.26/pith/color.c alpine-2.26.fillpara/pith/color.c
*** alpine-2.26/pith/color.c	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pith/color.c	2022-06-02 18:14:57.023136132 -0600
***************
*** 17,23 ****
  #include "../pith/state.h"
  #include "../pith/conf.h"
  #include "../pith/filter.h"
! 
  
  char *
  color_embed(char *fg, char *bg)
--- 17,24 ----
  #include "../pith/state.h"
  #include "../pith/conf.h"
  #include "../pith/filter.h"
! #include "../pith/mailview.h"
! #include "../pico/estruct.h"
  
  char *
  color_embed(char *fg, char *bg)
***************
*** 66,88 ****
      struct quote_colors *next;
  };
  
  
  int
  color_a_quote(long int linenum, char *line, LT_INS_S **ins, void *is_flowed_msg)
  {
!     int countem = 0;
      struct variable *vars = ps_global->vars;
!     char *p;
      struct quote_colors *colors = NULL, *cp, *next;
      COLOR_PAIR *col = NULL;
      int is_flowed = is_flowed_msg ? *((int *)is_flowed_msg) : 0;
  
      p = line;
!     if(!is_flowed)
!       while(isspace((unsigned char)*p))
! 	p++;
  
!     if(p[0] == '>'){
  	struct quote_colors *c;
  
  	/*
--- 67,176 ----
      struct quote_colors *next;
  };
  
+ int
+ is_word (buf, i, j)
+  char buf[NSTRING];
+  int i, j;
+ {
+  return i <= j && is_letter(buf[i]) ?
+          (i < j ? is_word(buf,i+1,j) : 1) : 0;
+ }
+ 
+ int
+ is_mailbox(buf,i,j)
+ char buf[NSTRING];
+  int i, j;
+ {
+   return i <= j && (is_letter(buf[i]) || is_digit(buf[i]) || buf[i] == '.')
+          ? (i < j ? is_mailbox(buf,i+1,j) : 1) : 0;
+ }
+ 
+ int
+ next_level_quote(buf, line, i, is_flowed)
+    char *buf;
+    char **line;
+    int i;
+    int is_flowed;
+ {
+    int j;
+ 
+    if (!single_level(buf[i])){
+         if(is_mailbox(buf,i,i)){
+           for (j = i; buf[j] && !isspace(buf[j]); j++);
+           if (is_word(buf,i,j-1) || is_mailbox(buf,i,j-1))
+            j += isspace(buf[j]) ? 2 : 1;
+         }
+         else{
+            switch(buf[i]){
+              case ':' :
+                       if (next(buf,i) != RPAREN)
+                            j = i + 1;
+                       else
+                            j = i + 2;
+                     break;
+ 
+              case '-' :
+                      if (next(buf,i) != '-')
+                         j = i + 2;
+                      else
+                         j = i + 3;
+                     break;
+ 
+              case '+' :
+              case '*' :
+                     if (next(buf,i) != ' ')
+                        j = i + 2;
+                     else
+                        j = i + 3;
+                     break;
+ 
+              default  :
+                    for (j = i; buf[j] && !isspace(buf[j])
+                          && (!single_level(buf[i]) && !is_letter(buf[j])); j++);
+ 
+                    j += isspace(buf[j]) ? 1 : 0;
+                    break;
+              }
+         }
+         if (line && *line)
+            (*line) += j - i;
+     }
+     else{
+        j = i+1;
+        if (line && *line)
+           (*line)++;
+     }
+     if(!is_flowed){
+         if(line && *line)
+           for(; isspace((unsigned char)*(*line)); (*line)++);
+         for (i = j; isspace((unsigned char) buf[i]); i++);
+     }
+     else i = j;
+     if (is_flowed && i != j)
+        buf[i] = '\0';
+    return i;
+ }
  
  int
  color_a_quote(long int linenum, char *line, LT_INS_S **ins, void *is_flowed_msg)
  {
!     int countem = 0, i, j = 0;
      struct variable *vars = ps_global->vars;
!     char *p, buf[NSTRING] = {'\0'};
      struct quote_colors *colors = NULL, *cp, *next;
      COLOR_PAIR *col = NULL;
      int is_flowed = is_flowed_msg ? *((int *)is_flowed_msg) : 0;
+     int code;
+ 
+     code = (is_flowed ? IS_FLOWED : NO_FLOWED) | COLORAQUO;
+     select_quote(linenum, line, ins, (void *) &code);
+     strncpy(buf, tmp_20k_buf, NSTRING < SIZEOF_20KBUF ? NSTRING : SIZEOF_20KBUF);
+     buf[sizeof(buf)-1] = '\0';
  
      p = line;
!     for(i = 0; isspace((unsigned char)buf[i]); i++, p++);
  
!     if(buf[i]){
  	struct quote_colors *c;
  
  	/*
***************
*** 131,137 ****
        free_color_pair(&col);
  
      cp = NULL;
!     while(*p == '>'){
  	cp = (cp && cp->next) ? cp->next : colors;
  
  	if(countem > 0)
--- 219,225 ----
        free_color_pair(&col);
  
      cp = NULL;
!     while(buf[i]){
  	cp = (cp && cp->next) ? cp->next : colors;
  
  	if(countem > 0)
***************
*** 141,150 ****
  
  	countem = (countem == 1) ? 0 : countem;
  
! 	p++;
! 	if(!is_flowed)
! 	  for(; isspace((unsigned char)*p); p++)
! 	    ;
      }
  
      if(colors){
--- 229,237 ----
  
  	countem = (countem == 1) ? 0 : countem;
  
!        i = next_level_quote(buf, &p, i, is_flowed);
!        for (; isspace((unsigned char)*p); p++);
!        for (; isspace((unsigned char)buf[i]); i++);
      }
  
      if(colors){
***************
*** 207,213 ****
  	}
      }
  
!     return(0);
  }
  
  
--- 294,300 ----
  	}
      }
  
!     return(1);
  }
  
  
diff -rc alpine-2.26/pith/color.h alpine-2.26.fillpara/pith/color.h
*** alpine-2.26/pith/color.h	2022-06-02 18:14:00.495274738 -0600
--- alpine-2.26.fillpara/pith/color.h	2022-06-02 18:14:57.027136121 -0600
***************
*** 22,27 ****
--- 22,45 ----
  #include "../pith/pattern.h"
  #include "../pith/osdep/color.h"
  
+ #define NO_FLOWED  0x0000
+ #define IS_FLOWED  0x0001
+ #define DELETEQUO  0x0010
+ #define COLORAQUO  0x0100
+ #define RAWSTRING  0x1000
+ 
+ /* This is needed for justification, I will move it to a better place later
+  * or maybe not
+  */
+ #define is_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0)
+ 
+ #define is_letter(c) (((c) >= 'a' && (c) <= 'z') || \
+                          ((c) >= 'A' && (c) <= 'Z'))
+ 
+ #define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0)
+ 
+ #define single_level(c) (((c) == '>') || ((c) == '|') || ((c) == '~') || \
+                           ((c) == ']'))
  
  typedef struct spec_color_s {
      int   inherit;	/* this isn't a color, it is INHERIT */
***************
*** 81,86 ****
--- 99,105 ----
  /* exported prototypes */
  char	*color_embed(char *, char *);
  int	 colorcmp(char *, char *);
+ int	 next_level_quote(char *, char **, int, int);
  int	 color_a_quote(long, char *, LT_INS_S **, void *);
  void	 free_spec_colors(SPEC_COLOR_S **);
  
diff -rc alpine-2.26/pith/filter.c alpine-2.26.fillpara/pith/filter.c
*** alpine-2.26/pith/filter.c	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pith/filter.c	2022-06-02 18:14:57.031136112 -0600
***************
*** 42,47 ****
--- 42,48 ----
  #include "../pith/conf.h"
  #include "../pith/store.h"
  #include "../pith/color.h"
+ #include "../pith/osdep/color.h"
  #include "../pith/escapes.h"
  #include "../pith/pipe.h"
  #include "../pith/status.h"
***************
*** 9503,9508 ****
--- 9504,9514 ----
  		margin_r,
  		indent;
      char	special[256];
+     long	curlinenum;	/* current line number */
+     int		curqstrpos;	/* current position in quote string */
+     long	linenum;	/* line number */
+     long	qstrlen;	/* multiples of 100 */
+     char      **qstrln;		/* qstrln[i] = quote string line i - 1 */
  } WRAP_S;
  
  #define	WRAP_MARG_L(F)	(((WRAP_S *)(F)->opt)->margin_l)
***************
*** 9544,9549 ****
--- 9550,9561 ----
  #define	WRAP_COLOR(F)	(((WRAP_S *)(F)->opt)->color)
  #define	WRAP_COLOR_SET(F)  ((WRAP_COLOR(F)) && (WRAP_COLOR(F)->fg[0]))
  #define	WRAP_SPACES(F)	(((WRAP_S *)(F)->opt)->spaces)
+ #define	WRAP_CURLINE(F)	(((WRAP_S *)(F)->opt)->curlinenum)
+ #define	WRAP_CURPOS(F)	(((WRAP_S *)(F)->opt)->curqstrpos)
+ #define	WRAP_LINENUM(F)	(((WRAP_S *)(F)->opt)->linenum)
+ #define	WRAP_QSTRLEN(F)	(((WRAP_S *)(F)->opt)->qstrlen)
+ #define	WRAP_QSTRN(F)	(((WRAP_S *)(F)->opt)->qstrln)
+ #define	WRAP_QSTR(F, N)	(((WRAP_S *)(F)->opt)->qstrln[(N)])
  #define	WRAP_PUTC(F,C,W) {						\
  			    if((F)->linep == WRAP_LASTC(F)){		\
  				size_t offset = (F)->linep - (F)->line;	\
***************
*** 9621,9626 ****
--- 9633,9640 ----
  	      case CCR :				/* CRLF or CR in text ? */
  		state = BOL;				/* either way, handle start */
  
+ 		WRAP_CURLINE(f)++;
+ 		WRAP_CURPOS(f) = 0;
  		if(WRAP_FLOW(f)){
  		    /* wrapped line? */
  		    if(f->f2 == 0 && WRAP_SPC_LEN(f) && WRAP_TRL_SPC(f)){
***************
*** 9714,9720 ****
  
  	      case BOL :
  		if(WRAP_FLOW(f)){
! 		    if(c == '>'){
  			WRAP_FL_QC(f) = 1;		/* init it */
  			state = FL_QLEV;		/* go collect it */
  		    }
--- 9728,9738 ----
  
  	      case BOL :
  		if(WRAP_FLOW(f)){
! 		    if(WRAP_CURLINE(f) < WRAP_QSTRLEN(f) 
! 			&& WRAP_QSTR(f, WRAP_CURLINE(f)) 
! 			&& WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)]
! 			&& WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] == c){
! 			WRAP_CURPOS(f)++;
  			WRAP_FL_QC(f) = 1;		/* init it */
  			state = FL_QLEV;		/* go collect it */
  		    }
***************
*** 9728,9734 ****
  			}
  
  			/* quote level change implies new paragraph */
! 			if(WRAP_FL_QD(f)){
  			    WRAP_FL_QD(f) = 0;
  			    if(WRAP_HARD(f) == 0){
  				WRAP_HARD(f) = 1;
--- 9746,9761 ----
  			}
  
  			/* quote level change implies new paragraph */
! 			if (WRAP_CURLINE(f) > 0 
! 			&& WRAP_CURLINE(f) < WRAP_QSTRLEN(f)
! 			&& (WRAP_QSTR(f, WRAP_CURLINE(f)) != NULL
! 			     || WRAP_QSTR(f, WRAP_CURLINE(f) - 1) != NULL)
! 			&& ((WRAP_QSTR(f, WRAP_CURLINE(f)) != NULL && 
! 			     WRAP_QSTR(f, WRAP_CURLINE(f) - 1) == NULL)
! 			   || (WRAP_QSTR(f, WRAP_CURLINE(f)) == NULL && 
! 			       WRAP_QSTR(f, WRAP_CURLINE(f) - 1) != NULL)
! 			   || strcmp(WRAP_QSTR(f, WRAP_CURLINE(f)),
! 				     WRAP_QSTR(f, WRAP_CURLINE(f) - 1)))){
  			    WRAP_FL_QD(f) = 0;
  			    if(WRAP_HARD(f) == 0){
  				WRAP_HARD(f) = 1;
***************
*** 9780,9787 ****
  		break;
  
  	      case  FL_QLEV :
! 		if(c == '>'){				/* another level */
! 		    WRAP_FL_QC(f)++;
  		}
  		else {
  		    /* if EMBEDed, process it and return here */
--- 9807,9818 ----
  		break;
  
  	      case  FL_QLEV :
! 		if(WRAP_CURLINE(f) < WRAP_QSTRLEN(f)
! 		   && WRAP_QSTR(f, WRAP_CURLINE(f)) 
! 		   && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)]
! 		   && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] == c){
! 		    WRAP_CURPOS(f)++;
! 		    WRAP_FL_QC(f)++; 			/* another level */
  		}
  		else {
  		    /* if EMBEDed, process it and return here */
***************
*** 9793,9799 ****
  		    }
  
  		    /* quote level change signals new paragraph */
! 		    if(WRAP_FL_QC(f) != WRAP_FL_QD(f)){
  			WRAP_FL_QD(f) = WRAP_FL_QC(f);
  			if(WRAP_HARD(f) == 0){		/* add hard newline */ 
  			    WRAP_HARD(f) = 1;		/* hard newline */
--- 9824,9839 ----
  		    }
  
  		    /* quote level change signals new paragraph */
! 		    if (WRAP_CURLINE(f) > 0 
! 			&& WRAP_CURLINE(f) < WRAP_QSTRLEN(f)
! 			&& (WRAP_QSTR(f, WRAP_CURLINE(f))
! 			     || WRAP_QSTR(f, WRAP_CURLINE(f) - 1))
! 			&& ((WRAP_QSTR(f, WRAP_CURLINE(f)) && 
! 			     !WRAP_QSTR(f, WRAP_CURLINE(f) - 1))
! 			   || (!WRAP_QSTR(f, WRAP_CURLINE(f)) && 
! 			       WRAP_QSTR(f, WRAP_CURLINE(f) - 1))
! 			   || strcmp(WRAP_QSTR(f, WRAP_CURLINE(f)),
! 				      WRAP_QSTR(f, WRAP_CURLINE(f) - 1)))){
  			WRAP_FL_QD(f) = WRAP_FL_QC(f);
  			if(WRAP_HARD(f) == 0){		/* add hard newline */ 
  			    WRAP_HARD(f) = 1;		/* hard newline */
***************
*** 9850,9855 ****
--- 9890,9902 ----
  		    state = FL_SIG;
  		    break;
  
+ 		  case ' ' :				/* what? */
+ 		   if (WRAP_CURLINE(f) < WRAP_QSTRLEN(f) 
+ 			&& WRAP_QSTR(f, WRAP_CURLINE(f))){
+ 			WRAP_SPC_LEN(f)++;
+ 			so_writec(' ', WRAP_SPACES(f));
+ 		   }
+ 
  		  default :				/* something else */
  		    state = DFL;
  		    goto case_dfl;			/* handle c like DFL */
***************
*** 9866,9872 ****
  					     &eob);      /* note any embedded*/
  			    wrap_eol(f, 1, &ip, &eib,
  				     &op, &eob);       /* plunk down newline */
! 			    wrap_bol(f, 1, 1, &ip, &eib,
  				     &op, &eob);         /* write any prefix */
  			}
  
--- 9913,9919 ----
  					     &eob);      /* note any embedded*/
  			    wrap_eol(f, 1, &ip, &eib,
  				     &op, &eob);       /* plunk down newline */
! 			    wrap_bol(f, 1, WRAP_FLOW(f), &ip, &eib,
  				     &op, &eob);         /* write any prefix */
  			}
  
***************
*** 10363,10369 ****
  		    wrap_flush_embed(f, &ip, &eib, &op, &eob);
  		    wrap_eol(f, 1, &ip, &eib, &op,
  			     &eob);	    /* plunk down newline */
! 		    wrap_bol(f,1,1, &ip, &eib, &op,
  			     &eob);	      /* write any prefix */
  		}
  
--- 10410,10416 ----
  		    wrap_flush_embed(f, &ip, &eib, &op, &eob);
  		    wrap_eol(f, 1, &ip, &eib, &op,
  			     &eob);	    /* plunk down newline */
! 		    wrap_bol(f,1,WRAP_FLOW(f), &ip, &eib, &op,
  			     &eob);	      /* write any prefix */
  		}
  
***************
*** 10436,10441 ****
--- 10483,10495 ----
  	if(WRAP_COLOR(f))
  	  free_color_pair(&WRAP_COLOR(f));
  
+ 	{ long i;
+ 	  for (i = 0L; i < WRAP_QSTRLEN(f); i++)
+ 	      if (WRAP_QSTR(f,i))
+ 		fs_give((void **) &(WRAP_QSTR(f,i)));
+ 	  fs_give((void **)&WRAP_QSTRN(f));
+ 	}
+ 
  	fs_give((void **) &f->line);	/* free temp line buffer */
  	so_give(&WRAP_SPACES(f));
  	fs_give((void **) &f->opt);	/* free wrap widths struct */
***************
*** 10786,10792 ****
  {
      int j, i;
      COLOR_PAIR *col = NULL;
!     char *prefix = NULL, *last_prefix = NULL;
  
      if(ps_global->VAR_QUOTE_REPLACE_STRING){
  	get_pair(ps_global->VAR_QUOTE_REPLACE_STRING, &prefix, &last_prefix, 0, 0);
--- 10840,10847 ----
  {
      int j, i;
      COLOR_PAIR *col = NULL;
!     char *prefix = NULL, *last_prefix = NULL, *wrap_qstr = NULL;
!     int level = 0, oldj, len;
  
      if(ps_global->VAR_QUOTE_REPLACE_STRING){
  	get_pair(ps_global->VAR_QUOTE_REPLACE_STRING, &prefix, &last_prefix, 0, 0);
***************
*** 10795,10804 ****
  	    last_prefix = NULL;
  	}
      }
! 
!     for(j = 0; j < WRAP_FL_QD(f); j++){
  	if(WRAP_USE_CLR(f)){
! 	    if((j % 3) == 0
  	       && ps_global->VAR_QUOTE1_FORE_COLOR
  	       && ps_global->VAR_QUOTE1_BACK_COLOR
  	       && (col = new_color_pair(ps_global->VAR_QUOTE1_FORE_COLOR,
--- 10850,10871 ----
  	    last_prefix = NULL;
  	}
      }
!     
!     if(WRAP_CURLINE(f) < WRAP_QSTRLEN(f) && WRAP_QSTR(f, WRAP_CURLINE(f)))
!        wrap_qstr = cpystr(WRAP_QSTR(f, WRAP_CURLINE(f)));
!     len = wrap_qstr ? strlen(wrap_qstr) : 0;
! 
!     for (j = wrap_qstr && *wrap_qstr == ' ' ? 1 : 0;
! 		 j < len && isspace((unsigned char)wrap_qstr[j]); j++){
! 	GF_PUTC_GLO(f->next, wrap_qstr[j]);
!         f->n += ((wrap_qstr[j] == TAB) ? (~f->n & 0x07) + 1 : 1);
!     }
!     
!     for(; j < len && level < len; level++){
!         oldj = j;
!         j = next_level_quote(wrap_qstr, (char **)NULL, j, WRAP_FLOW(f));
  	if(WRAP_USE_CLR(f)){
! 	    if((level % 3) == 0
  	       && ps_global->VAR_QUOTE1_FORE_COLOR
  	       && ps_global->VAR_QUOTE1_BACK_COLOR
  	       && (col = new_color_pair(ps_global->VAR_QUOTE1_FORE_COLOR,
***************
*** 10806,10812 ****
  	       && pico_is_good_colorpair(col)){
                  GF_COLOR_PUTC(f, col);
              }
! 	    else if((j % 3) == 1
  		    && ps_global->VAR_QUOTE2_FORE_COLOR
  		    && ps_global->VAR_QUOTE2_BACK_COLOR
  		    && (col = new_color_pair(ps_global->VAR_QUOTE2_FORE_COLOR,
--- 10873,10879 ----
  	       && pico_is_good_colorpair(col)){
                  GF_COLOR_PUTC(f, col);
              }
! 	    else if((level % 3) == 1
  		    && ps_global->VAR_QUOTE2_FORE_COLOR
  		    && ps_global->VAR_QUOTE2_BACK_COLOR
  		    && (col = new_color_pair(ps_global->VAR_QUOTE2_FORE_COLOR,
***************
*** 10814,10820 ****
  		    && pico_is_good_colorpair(col)){
  	        GF_COLOR_PUTC(f, col);
              }
! 	    else if((j % 3) == 2
  		    && ps_global->VAR_QUOTE3_FORE_COLOR
  		    && ps_global->VAR_QUOTE3_BACK_COLOR
  		    && (col = new_color_pair(ps_global->VAR_QUOTE3_FORE_COLOR,
--- 10881,10887 ----
  		    && pico_is_good_colorpair(col)){
  	        GF_COLOR_PUTC(f, col);
              }
! 	    else if((level % 3) == 2
  		    && ps_global->VAR_QUOTE3_FORE_COLOR
  		    && ps_global->VAR_QUOTE3_BACK_COLOR
  		    && (col = new_color_pair(ps_global->VAR_QUOTE3_FORE_COLOR,
***************
*** 10828,10870 ****
  	    }
  	}
  
  	if(!WRAP_LV_FLD(f)){
  	    if(!WRAP_FOR_CMPS(f) && ps_global->VAR_QUOTE_REPLACE_STRING && prefix){
  		for(i = 0; prefix[i]; i++)
  		  GF_PUTC_GLO(f->next, prefix[i]);
! 		f->n += utf8_width(prefix);
! 	    }
! 	    else if(ps_global->VAR_REPLY_STRING
! 		    && (!strcmp(ps_global->VAR_REPLY_STRING, ">")
! 			|| !strcmp(ps_global->VAR_REPLY_STRING, "\">\""))){
! 		GF_PUTC_GLO(f->next, '>');
! 		f->n += 1;
  	    }
  	    else{
! 		GF_PUTC_GLO(f->next, '>');
! 		GF_PUTC_GLO(f->next, ' ');
! 		f->n += 2;
  	    }
  	}
  	else{
! 	    GF_PUTC_GLO(f->next, '>');
! 	    f->n += 1;
  	}
      }
      if(j && WRAP_LV_FLD(f)){
  	GF_PUTC_GLO(f->next, ' ');
  	f->n++;
      }
!     else if(j && last_prefix){
  	for(i = 0; last_prefix[i]; i++)
  	  GF_PUTC_GLO(f->next, last_prefix[i]);
! 	f->n += utf8_width(last_prefix);	
      }
  
      if(prefix)
        fs_give((void **)&prefix);
      if(last_prefix)
        fs_give((void **)&last_prefix);
  
      return 0;
  }
--- 10895,10941 ----
  	    }
  	}
  
+ 	if (j > 1 && wrap_qstr[j-1] == ' ')
+ 	   j -= 1; 
+ 
  	if(!WRAP_LV_FLD(f)){
  	    if(!WRAP_FOR_CMPS(f) && ps_global->VAR_QUOTE_REPLACE_STRING && prefix){
  		for(i = 0; prefix[i]; i++)
  		  GF_PUTC_GLO(f->next, prefix[i]);
! 		f->n += utf8_widthis(prefix);
  	    }
  	    else{
! 	      for (i = oldj; i < j; i++)   
! 		GF_PUTC_GLO(f->next, wrap_qstr[i]);
! 	      f->n += j - oldj;
  	    }
  	}
  	else{
! 	    for (i = oldj; i < j; i++)   
! 		GF_PUTC_GLO(f->next, wrap_qstr[i]);
! 	    f->n += j - oldj;
  	}
+ 	for (i = j; isspace((unsigned char)wrap_qstr[i]); i++)
+ 	       GF_PUTC_GLO(f->next, ' ');
+ 	    f->n += i - j;
+ 	for (; isspace((unsigned char)wrap_qstr[j]); j++);
      }
      if(j && WRAP_LV_FLD(f)){
  	GF_PUTC_GLO(f->next, ' ');
  	f->n++;
      }
!     else if(j && !value_is_space(wrap_qstr) && last_prefix){
  	for(i = 0; last_prefix[i]; i++)
  	  GF_PUTC_GLO(f->next, last_prefix[i]);
! 	f->n += utf8_widthis(last_prefix);	
      }
  
      if(prefix)
        fs_give((void **)&prefix);
      if(last_prefix)
        fs_give((void **)&last_prefix);
+     if (wrap_qstr)
+       fs_give((void **)&wrap_qstr);  
  
      return 0;
  }
***************
*** 10896,10901 ****
--- 10967,10978 ----
      wrap->hdr_color    = (GFW_HDRCOLOR & flags) == GFW_HDRCOLOR;
      wrap->for_compose  = (GFW_FORCOMPOSE & flags) == GFW_FORCOMPOSE;
      wrap->handle_soft_hyphen = (GFW_SOFTHYPHEN & flags) == GFW_SOFTHYPHEN;
+     wrap->curlinenum   = 0L;
+     wrap->curqstrpos   = 0;
+     wrap->linenum      = 0L;
+     wrap->qstrlen      = 100L;
+     wrap->qstrln       = (char **) fs_get(100*sizeof(char *));
+     memset(wrap->qstrln, 0, 100*sizeof(char *));
  
      return((void *) wrap);
  }
***************
*** 11339,11345 ****
--- 11416,11630 ----
  			    } \
  			}
  
+ #define ADD_QUOTE_STRING(F) {						\
+ 	int len = tmp_20k_buf[0] ? strlen(tmp_20k_buf) + 1 : 0;		\
+ 	FILTER_S *fltr;							\
+ 									\
+ 	for(fltr = (F); fltr && fltr->f != gf_wrap; fltr = fltr->next); \
+ 	if (fltr){							\
+ 	   if (WRAP_LINENUM(fltr) >= WRAP_QSTRLEN(fltr)){		\
+ 	      fs_resize((void **)&WRAP_QSTRN(fltr),			\
+ 			(WRAP_QSTRLEN(fltr) + 100) * sizeof(char *));	\
+ 	      memset(WRAP_QSTRN(fltr)+WRAP_QSTRLEN(fltr), 0, 		\
+ 						100*sizeof(char*));	\
+ 	      WRAP_QSTRLEN(fltr) += 100L;				\
+ 	   }								\
+ 	   if (len){							\
+ 	      WRAP_QSTR(fltr, WRAP_LINENUM(fltr)) = 			\
+ 				(char *) fs_get(len*sizeof(char));	\
+ 	      WRAP_QSTR(fltr, WRAP_LINENUM(fltr)) = cpystr(tmp_20k_buf);\
+ 	   }								\
+ 	   WRAP_LINENUM(fltr)++;					\
+ 	}								\
+ }
+ 
+ int end_of_line(char *line)
+ {
+   int i;
+ 
+   for(i= 0; line && line[i]; i++){
+      if((line[i] == '\015' && line[i+1] == '\012') || line[i] == '\012')
+ 	break;
+   }
+   return i;
+ }
+ 
+ /* This macro is used in gf_quote_test. It receives a return code
+    from a filter. All filters that will print something must send
+    return code 0, except color_a_quote which must send return code
+    1
+  */
+ 
+ #define GF_ADD_QUOTED_LINE(F, line) \
+ { \
+     LT_INS_S *ins = NULL, *insp; \
+     int done; \
+     char *gline, *cline;\
+     unsigned char  ch;\
+     register char *cp;\
+     register int   l;\
+ 	\
+     for (gline = cline = line; gline && cline; ){\
+ 	if((cline = strchr(gline,'\012')) != NULL)\
+ 	  *cline = '\0';\
+ 	done = (*((LINETEST_S *) (F)->opt)->f)((F)->n++, gline, &ins,\
+ 			   ((LINETEST_S *) (F)->opt)->local);\
+ 	if (done < 2){ \
+ 	   if(done == 1)\
+ 	     ADD_QUOTE_STRING((F));\
+ 	   for(insp = ins,  cp = gline; *cp ; ){\
+ 	       if(insp && cp == insp->where){\
+ 		if(insp->len > 0){ \
+ 	          for(l = 0; l < insp->len; l++){\
+ 			  ch =  (unsigned char) insp->text[l];\
+ 		     GF_PUTC((F)->next, ch);\
+ 	          }\
+ 	          insp = insp->next;\
+ 		  continue; \
+ 		} else if(insp->len < 0){ \
+ 		  cp -= insp->len; \
+ 		  insp = insp->next; \
+ 		  continue; \
+ 		} \
+ 	       }\
+ 	       GF_PUTC((F)->next, *cp);\
+ 	       cp++;\
+ 	   }\
+ 	   while(insp){\
+ 	       for(l = 0; l < insp->len; l++){\
+ 	          ch = (unsigned char) insp->text[l];\
+ 	          GF_PUTC((F)->next, ch);\
+ 	       }\
+ 	       insp = insp->next;\
+ 	   }\
+ 	   gf_line_test_free_ins(&ins);\
+ 	   if(cline){ \
+ 	     *cline = '\012';\
+ 	     gline += cline - gline + 1;\
+ 	   }\
+ 	   GF_PUTC((F)->next, '\015');\
+ 	   GF_PUTC((F)->next, '\012');\
+ 	}\
+    }\
+ }
+ /* test second line of old line first */
+ #define SECOND_LINE_QUOTE_TEST(line, F) \
+ {\
+ 	*p = '\0';\
+ 	i = end_of_line((F)->oldline); \
+ 	if (((F)->oldline)[i]){\
+ 	   i += (((F)->oldline)[i] == '\015') ? 2 : 1;\
+ 	   line = (F)->oldline + i;\
+ 	   i = end_of_line(line); \
+ 	   if(line[i])\
+ 	     line[i] = '\0'; \
+ 	}\
+ 	for (i = 0; ((F)->line) \
+ 		&& (i < LINE_TEST_BLOCK) \
+ 		&& (i < SIZEOF_20KBUF)\
+ 		&& ((F)->line)[i] \
+ 		&& (((F)->line)[i] != '\015')\
+ 		&& (((F)->line)[i] != '\012')\
+ 		&& (tmp_20k_buf[i] = ((F)->line)[i]); i++);\
+ 	tmp_20k_buf[i] = '\0';\
+ 	GF_ADD_QUOTED_LINE((F), line);\
+ }
+ 
+ #define FIRST_LINE_QUOTE_TEST(line, F)\
+ {\
+ 	*p = '\0';\
+ 	line = (F)->line;\
+ 	if ((F)->oldline)\
+ 	   fs_give((void **)&(F)->oldline);\
+ 	(F)->oldline = cpystr(line);\
+ 	i = end_of_line(line); \
+ 	if (line[i]){ \
+ 	   j = (line[i] == '\015') ? 2 : 1;\
+ 	   line[i] = '\0'; \
+ 	   i += j; \
+ 	}\
+ 	for (j = 0; ((F)->line) \
+ 		&& ((i + j) < LINE_TEST_BLOCK) \
+ 		&& (j < SIZEOF_20KBUF) \
+ 		&& ((F)->line)[i + j] \
+ 		&& (((F)->line)[i + j] != '\015')\
+ 		&& (((F)->line)[i + j] != '\012')\
+ 		&& (tmp_20k_buf[j] = ((F)->line)[i + j]); j++);\
+ 	tmp_20k_buf[j] = '\0';\
+ 	GF_ADD_QUOTED_LINE((F), line);\
+ }
+ 
+ 
+ void
+ gf_quote_test(f, flg)
+     FILTER_S *f;
+     int	      flg;
+ {
+     register char *p = f->linep;
+     register char *eobuf = GF_LINE_TEST_EOB(f);
+     char *line = NULL; 
+     int i, j;
+     GF_INIT(f, f->next);
+ 
+     if(flg == GF_DATA){
+ 	register unsigned char c;
+ 	register int state = f->f1;
+ 
+ 	while(GF_GETC(f, c)){
+ 
+ 	    GF_LINE_TEST_ADD(f, c);
+ 	    if(c == '\012')
+ 	      state++;
+ 	    if(state == 2){		/* two full lines read */
+ 		state = 0;
+ 
+ 		/* first process the second line of an old line */
+ 		if (f->oldline && f->oldline[0])
+ 		    SECOND_LINE_QUOTE_TEST(line, f);
+ 
+ 		/* now we process the first line */
+ 		FIRST_LINE_QUOTE_TEST(line, f);
+ 
+ 		p = f->line;
+ 	    }
+ 	}
+ 
+ 	f->f1 = state;
+ 	GF_END(f, f->next);
+     }
+     else if(flg == GF_EOD){
+         /* first process the second line of an old line */
+ 	if (f->oldline && f->oldline[0])
+ 	    SECOND_LINE_QUOTE_TEST(line, f);
+ 
+ 	/* now we process the first line */
+ 	FIRST_LINE_QUOTE_TEST(line, f);
+ 
+ 	/* We are out of data. In this case we have processed the second
+ 	 * line of an oldline, then the first line of a line, but we need
+ 	 * to process the second line of the given line. We do this by
+ 	 * processing it now!.
+ 	 */
+ 	if (line[i]){
+ 	   tmp_20k_buf[0] = '\0';	/* No next line */
+ 	   GF_ADD_QUOTED_LINE(f, line+i);
+ 	}
+ 
+ 	fs_give((void **) &f->oldline); /* free old line buffer */
+ 	fs_give((void **) &f->line);	/* free line buffer */
+ 	fs_give((void **) &f->opt);	/* free test struct */
+ 	GF_FLUSH(f->next);
+ 	(*f->next->f)(f->next, GF_EOD);
+     }
+     else if(flg == GF_RESET){
+ 	f->f1 = 0;			/* state */
+ 	f->n  = 0L;			/* line number */
+ 	f->f2 = LINE_TEST_BLOCK;	/* size of alloc'd line */
+ 	f->line = p = (char *) fs_get(f->f2 * sizeof(char));
+     }
  
+     f->linep = p;
+ }
  
  /*
   * this simple filter accumulates characters until a newline, offers it
diff -rc alpine-2.26/pith/filter.h alpine-2.26.fillpara/pith/filter.h
*** alpine-2.26/pith/filter.h	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pith/filter.h	2022-06-02 18:14:57.031136112 -0600
***************
*** 217,222 ****
--- 217,223 ----
  void	   *gf_prepend_editorial_opt(prepedtest_t, char *);
  void	    gf_nvtnl_local(FILTER_S *, int);
  void	    gf_local_nvtnl(FILTER_S *, int);
+ void	    gf_quote_test(FILTER_S *, int);
  void	   *gf_url_hilite_opt(URL_HILITE_S *, HANDLE_S **, int);
  void	    free_filter_module_globals(void);
  
diff -rc alpine-2.26/pith/filttype.h alpine-2.26.fillpara/pith/filttype.h
*** alpine-2.26/pith/filttype.h	2022-06-02 18:14:00.495274738 -0600
--- alpine-2.26.fillpara/pith/filttype.h	2022-06-02 18:14:57.031136112 -0600
***************
*** 36,41 ****
--- 36,43 ----
      unsigned char t;		/* temporary char                        */
      char     *line;		/* place for temporary storage           */
      char     *linep;		/* pointer into storage space            */
+     char     *oldline;		/* the previous line to "line"		 */
+     char     *oldlinep;		/* the previous line to "line"		 */
      void     *opt;		/* optional per instance data		 */
      void     *data;		/* misc internal data pointer		 */
      unsigned char queue[1 + GF_MAXBUF];
diff -rc alpine-2.26/pith/mailview.c alpine-2.26.fillpara/pith/mailview.c
*** alpine-2.26/pith/mailview.c	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pith/mailview.c	2022-06-02 18:14:57.035136102 -0600
***************
*** 51,57 ****
  #include "../pith/escapes.h"
  #include "../pith/keyword.h"
  #include "../pith/smime.h"
! 
  
  #define FBUF_LEN	(50)
  
--- 51,61 ----
  #include "../pith/escapes.h"
  #include "../pith/keyword.h"
  #include "../pith/smime.h"
! #include "../pith/osdep/color.h"
! #include "../pico/osdep/color.h"
! #include "../pico/estruct.h"
! #include "../pico/pico.h"
! #include "../pico/efunc.h"
  
  #define FBUF_LEN	(50)
  
***************
*** 634,640 ****
  	       && pico_usingcolor()
  	       && ps_global->VAR_SIGNATURE_FORE_COLOR
  	       && ps_global->VAR_SIGNATURE_BACK_COLOR){
! 		gf_link_filter(gf_line_test, gf_line_test_opt(color_signature, &is_in_sig));
  	    }
  
  	    if((flgs & FM_DISPLAY)
--- 638,644 ----
  	       && pico_usingcolor()
  	       && ps_global->VAR_SIGNATURE_FORE_COLOR
  	       && ps_global->VAR_SIGNATURE_BACK_COLOR){
! 		gf_link_filter(gf_quote_test, gf_line_test_opt(color_signature, &is_in_sig));
  	    }
  
  	    if((flgs & FM_DISPLAY)
***************
*** 642,649 ****
  	       && pico_usingcolor()
  	       && ps_global->VAR_QUOTE1_FORE_COLOR
  	       && ps_global->VAR_QUOTE1_BACK_COLOR){
! 		gf_link_filter(gf_line_test, gf_line_test_opt(color_a_quote, NULL));
  	    }
  
  	    if(!(flgs & FM_NOWRAP)){
  		wrapflags = (flgs & FM_DISPLAY) ? (GFW_HANDLES|GFW_SOFTHYPHEN) : GFW_NONE;
--- 646,655 ----
  	       && pico_usingcolor()
  	       && ps_global->VAR_QUOTE1_FORE_COLOR
  	       && ps_global->VAR_QUOTE1_BACK_COLOR){
! 		gf_link_filter(gf_quote_test, gf_line_test_opt(color_a_quote, NULL));
  	    }
+ 	    else
+ 		gf_link_filter(gf_quote_test,gf_line_test_opt(select_quote, NULL));
  
  	    if(!(flgs & FM_NOWRAP)){
  		wrapflags = (flgs & FM_DISPLAY) ? (GFW_HANDLES|GFW_SOFTHYPHEN) : GFW_NONE;
***************
*** 1459,1485 ****
  color_signature(long int linenum, char *line, LT_INS_S **ins, void *is_in_sig)
  {
      struct variable *vars = ps_global->vars;
!     int             *in_sig_block;
      COLOR_PAIR      *col = NULL;
  
      if(is_in_sig == NULL)
        return 0;
  
      in_sig_block = (int *) is_in_sig;
      
!     if(!strcmp(line, SIGDASHES))
!       *in_sig_block = START_SIG_BLOCK; 
!     else if(*line == '\0')
        /* 
         * Suggested by Eduardo: allow for a blank line right after 
         * the sigdashes. 
         */
        *in_sig_block = (*in_sig_block == START_SIG_BLOCK)
  			  ? IN_SIG_BLOCK : OUT_SIG_BLOCK;
      else
        *in_sig_block = (*in_sig_block != OUT_SIG_BLOCK)
  			  ? IN_SIG_BLOCK : OUT_SIG_BLOCK;
  
      if(*in_sig_block != OUT_SIG_BLOCK
         && VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR
         && (col = new_color_pair(VAR_SIGNATURE_FORE_COLOR,
--- 1465,1552 ----
  color_signature(long int linenum, char *line, LT_INS_S **ins, void *is_in_sig)
  {
      struct variable *vars = ps_global->vars;
!     int             *in_sig_block, i, j,same_qstr = 0, plb;
      COLOR_PAIR      *col = NULL;
+     static char GLine[NSTRING] = {'\0'};
+     static char PLine[NSTRING] = {'\0'};
+     static char PPLine[NSTRING] = {'\0'};
+     char NLine[NSTRING] = {'\0'};
+     char rqstr[NSTRING] = {'\0'};
+     char *p, *q;
+     static char *buf, buf2[NSTRING] = {'\0'};
+     QSTRING_S *qs;
+     static int qstrlen = 0;
  
      if(is_in_sig == NULL)
        return 0;
  
+     if (linenum > 0){
+ 	strncpy(PLine, GLine, sizeof(PLine));
+ 	PLine[sizeof(PLine)-1] = '\0';
+     }
+ 
+     if((p = strchr(tmp_20k_buf, '\015')) != NULL) *p = '\0';
+     strncpy(NLine, tmp_20k_buf, sizeof(NLine));
+     NLine[sizeof(NLine) - 1] = '\0';
+     if (p) *p = '\015';
+ 
+     strncpy(GLine, line, sizeof(GLine));
+     GLine[sizeof(GLine) - 1] = '\0';
+ 
+     ps_global->list_qstr = default_qstr(ps_global->prefix && *ps_global->prefix 
+ 		? (void *) ps_global->prefix : (void *) ">", 0);
+     plb = line_isblank(ps_global->list_qstr, PLine, GLine, PPLine, NSTRING);
+     qs = do_quote_match(ps_global->list_qstr, GLine, NLine, PLine, rqstr, NSTRING, plb);
+     if(linenum > 0)
+        strncpy(PPLine, PLine, NSTRING);
+     strncpy(buf2, rqstr, NSTRING);
+     i = buf2 && buf2[0] ? strlen(buf2) : 0;
+     free_qs(&qs);
+ 
+     /* determine if buf and buf2 are the same quote string */
+     if (!struncmp(buf, buf2, qstrlen)){
+       for (j = qstrlen; buf2[j] && isspace((unsigned char)buf2[j]); j++);
+       if (!buf2[j] || buf2[j] == '|' || (buf2[j] == '*' && buf2[j+1] != '>'))
+          same_qstr++;
+     }
+ 
      in_sig_block = (int *) is_in_sig;
      
!     if (*in_sig_block != OUT_SIG_BLOCK){
!       if (line && *line && (strlen(line) >= qstrlen) && same_qstr)
!          line += qstrlen;
!         else if (strlen(line) < qstrlen)
!          line += i;
!       else if (!same_qstr)
!          *in_sig_block = OUT_SIG_BLOCK;
!     }
!     else
!       line += i;
! 
!     if(!strcmp(line, SIGDASHES) || !strcmp(line, "--")){
!       *in_sig_block = START_SIG_BLOCK;
!        buf = (char *) fs_get((i + 1)*sizeof(char));
!        buf = cpystr(buf2);
!        qstrlen = i;
!     }
!     else if(*line == '\0'){
        /* 
         * Suggested by Eduardo: allow for a blank line right after 
         * the sigdashes. 
         */
        *in_sig_block = (*in_sig_block == START_SIG_BLOCK)
  			  ? IN_SIG_BLOCK : OUT_SIG_BLOCK;
+     }
      else
        *in_sig_block = (*in_sig_block != OUT_SIG_BLOCK)
  			  ? IN_SIG_BLOCK : OUT_SIG_BLOCK;
  
+     if (*in_sig_block == OUT_SIG_BLOCK){
+       qstrlen = 0;    /* reset back in case there's another paragraph */
+       if (buf)
+          fs_give((void **)&buf);
+     }
+ 
      if(*in_sig_block != OUT_SIG_BLOCK
         && VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR
         && (col = new_color_pair(VAR_SIGNATURE_FORE_COLOR,
***************
*** 2039,2044 ****
--- 2106,2182 ----
  }
  
  
+ /* This filter gives a quote string of a line. It sends its reply back to the
+    calling filter in the tmp_20k_buf variable. This filter replies with
+    the full quote string including tailing spaces if any. It is the
+    responsibility of the calling filter to figure out if thos spaces are
+    useful for that filter or if they should be removed before doing any
+    useful work. For example, color_a_quote does not require the trailing
+    spaces, but gf_wrap does.
+  */
+ int
+ select_quote(long linenum, char *line, LT_INS_S **ins, void *local)
+ {
+      int i, plb, *code;
+      char rqstr[NSTRING] = {'\0'}, buf[NSTRING] = {'\0'};
+      char GLine[NSTRING] = {'\0'}, PLine[NSTRING] = {'\0'};
+      char PPLine[NSTRING] = {'\0'}, NLine[NSTRING] = {'\0'};
+      static char GLine1[NSTRING] = {'\0'};
+      static char PLine1[NSTRING] = {'\0'};
+      static char PPLine1[NSTRING] = {'\0'};
+      static char GLine2[NSTRING] = {'\0'};
+      static char PLine2[NSTRING] = {'\0'};
+      static char PPLine2[NSTRING] = {'\0'};
+      QSTRING_S *qs;
+      int buflen = NSTRING < SIZEOF_20KBUF ? NSTRING - 1: SIZEOF_20KBUF - 1;
+      int who, raw;
+ 
+      code = (int *)local;
+      who = code ? (*code & COLORAQUO) : 0; /* may I ask who is calling? */
+      raw = code ? (*code & RAWSTRING) : 0; /* return raw string */
+      strncpy(GLine, (who ? GLine1 : GLine2), buflen);
+      strncpy(PLine, (who ? PLine1 : PLine2), buflen);
+      strncpy(PPLine, (who ? PPLine1 : PPLine2), buflen);
+ 
+      if (linenum > 0)
+         strncpy(PLine, GLine, buflen);
+ 
+      strncpy(NLine, tmp_20k_buf, buflen);
+ 
+      if (line)
+         strncpy(GLine, line, buflen);
+      else
+         GLine[0] = '\0';
+ 
+      ps_global->list_qstr = default_qstr(ps_global->prefix && *ps_global->prefix 
+ 		? (void *) ps_global->prefix : (void *) ">", 0);
+      plb = line_isblank(ps_global->list_qstr, PLine, GLine, PPLine, NSTRING);
+ 
+      qs = do_quote_match(ps_global->list_qstr, GLine, NLine, PLine, rqstr, NSTRING, plb);
+      if (raw)
+         strncpy(buf, rqstr, NSTRING);
+      else
+         flatten_qstring(qs, buf, NSTRING);
+      if(qs)
+ 	record_quote_string(qs);
+      free_qs(&qs);
+ 
+      /* do not paint an extra level for a line with a >From string at the
+       * begining of it
+       */
+      if (buf[0]){
+        i = strlen(buf);
+        if (strlen(line) >= i + 6 && !strncmp(line+i-1,">From ", 6))
+            buf[i - 1] = '\0';
+      }
+      strncpy(tmp_20k_buf, buf, buflen);
+      if (linenum > 0)
+        strncpy((who ? PPLine1 : PPLine2), PLine, buflen);
+      strncpy((who ? GLine1 : GLine2), GLine, buflen);
+      strncpy((who ? PLine1 : PLine2), PLine, buflen);
+      return 1;
+ }
+ 
  
  #define	UES_LEN	12
  #define	UES_MAX	32
diff -rc alpine-2.26/pith/mailview.h alpine-2.26.fillpara/pith/mailview.h
*** alpine-2.26/pith/mailview.h	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pith/mailview.h	2022-06-02 18:14:57.035136102 -0600
***************
*** 146,151 ****
--- 146,152 ----
  char	   *display_parameters(PARAMETER *);
  char	   *pine_fetch_header(MAILSTREAM *, long, char *, char **, long);
  int         color_signature(long, char *, LT_INS_S **, void *);
+ int	    select_quote(long, char *, LT_INS_S **, void *);
  int	    scroll_handle_start_color(char *, size_t, int *);
  int	    scroll_handle_end_color(char *, size_t, int *, int);
  int         width_at_this_position(unsigned char *, unsigned long);
diff -rc alpine-2.26/pith/osdep/color.c alpine-2.26.fillpara/pith/osdep/color.c
*** alpine-2.26/pith/osdep/color.c	2022-06-02 18:14:00.495274738 -0600
--- alpine-2.26.fillpara/pith/osdep/color.c	2022-06-02 18:14:57.039136093 -0600
***************
*** 28,34 ****
  
  #include <system.h>
  #include "./color.h"
! 
  
  
  /*
--- 28,36 ----
  
  #include <system.h>
  #include "./color.h"
! #include "./collate.h"
! #include "../charconv/utf8.h"
! #include "../../c-client/c-client.h"
  
  
  /*
***************
*** 88,90 ****
--- 90,1372 ----
  {
      return(pico_set_colors(col ? col->fg : NULL, col ? col->bg : NULL, flags));
  }
+ 
+ 
+   /* 
+    * Extended Justification support also does not belong here
+    * but otherwise webpine will not build, so we move everything
+    * here. Hopefully this will be the permanent place for these
+    * routines. These routines used to be in pico/word.c
+    */
+ #define NSTRING 256
+ #include "../../include/general.h"
+ 
+ /* Support of indentation of paragraphs */
+ #define is_indent_char(c)  (((c) == '.' || (c) == '}' || (c) == RPAREN || \
+ 			     (c) == '*' || (c) == '+' || is_a_digit(c) || \
+ 			     ISspace(c) || (c) == '-' || \
+ 			     (c) == ']') ? 1 : 0)
+ #define allowed_after_digit(c,word,k)  ((((c) == '.' && \
+ 			     allowed_after_period(next((word),(k))))  ||\
+ 				(c) == RPAREN || (c) == '}' || (c) == ']' ||\
+ 				  ISspace(c) ||  is_a_digit(c) || \
+ 				  (((c) == '-' ) && \
+ 				    allowed_after_dash(next((word),(k))))) \
+ 				? 1 : 0)
+ #define allowed_after_period(c)	 (((c) == RPAREN || (c) == '}' || (c) == ']' ||\
+ 				   ISspace(c) || (c) == '-' || \
+ 				   is_a_digit(c)) ? 1 : 0)
+ #define allowed_after_parenth(c)  (ISspace(c) ? 1 : 0)
+ #define allowed_after_space(c)	  (ISspace(c) ? 1 : 0)
+ #define allowed_after_braces(c)	  (ISspace(c) ? 1 : 0)
+ #define allowed_after_star(c)	 ((ISspace(c) || (c) == RPAREN ||\
+                                        (c) == ']' || (c) == '}') ? 1 : 0)
+ #define allowed_after_dash(c)	  ((ISspace(c) || is_a_digit(c)) ? 1 : 0)
+ #define EOLchar(c)		  (((c) == '.' || (c) == ':' || (c) == '?' ||\
+ 					(c) == '!') ? 1 : 0)
+ 
+ 
+ /* Extended justification support */
+ #define is_cquote(c) ((c) == '>' || (c) == '|' || (c) == ']' || (c) == ':')
+ #define is_cword(c)  ((((c) >= 'a') && ((c) <= 'z')) ||  \
+                      (((c) >= 'A') && ((c) <= 'Z')) || \
+                      (((c) >= '0') && ((c) <= '9')) || \
+                       ((c) == ' ') || ((c) == '?') || \
+                       ((c) == '@') || ((c) == '.') || \
+                       ((c) == '!') || ((c) == '\'') || \
+                       ((c) == ',') || ((c) == '\"') ? 1 : 0)
+ #define isaquote(c)   ((c) == '\"' || (c) == '\'')
+ #define is8bit(c)     ((((int) (c)) & 0x80) ? 1 : 0)
+ #define iscontrol(c)  (iscntrl(((int) (c)) & 0x7f) ? 1 : 0)
+ #define forbidden(c)  (((c) == '\"') || ((c) == '\'') || ((c) == '$') ||\
+                        ((c) == ',')  || ((c) == '.')  || ((c) == '-') ||\
+                        ((c) == LPAREN) || ((c) == '/')|| ((c) == '`') ||\
+                        ((c) == '{') || ((c) == '\\') || (iscontrol((c))) ||\
+                        (((c) >= '0')  && ((c) <= '9')) || ((c) == '?'))
+ #define is_cletter(c)  (((((c) >= 'a') && ((c) <= 'z'))) ||\
+                        ((((c) >= 'A') && ((c) <= 'Z'))||\
+                       is8bit(c)))
+ #define is_cnumber(c) ((c) >= '0' && (c) <= '9')
+ #define allwd_after_word(c) (((c) == ' ') || ((c) == '>') || is_cletter(c))
+ #define allwd_after_qsword(c)  (((c) != '\\') && ((c) != RPAREN) && ((c) !=  '/'))
+ #define before(word,i) (((i) > 0) ? (word)[(i) - 1] : 0)
+ #define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0)
+ #define now(w,i)  ((w)[(i)])
+ #define is_qsword(c)  (((c) == ':') || ((c) == RPAREN) ? 1 : 0)
+ #define is_colon(c)   (((c) == ':') ? 1 : 0)
+ #define is_rarrow(c)  (((c) == '>') ? 1 : 0)
+ #define is_tilde(c)   (((c) == '~') ? 1 : 0)
+ #define is_dash(c)    (((c) == '-') ? 1 : 0)
+ #define is_pound(c)   (((c) == '#') ? 1 : 0)
+ #define is_a_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0)
+ #define is_allowed(c)  (is_cquote(c) || is_cword(c) || is_dash(c) || \
+                        is_pound(c))
+ #define qs_allowed(a)  (((a)->qstype != qsGdb) && ((a)->qstype != qsProg))
+ 
+ /* Internal justification functions */
+ QSTRING_S *is_quote(char **, char *, int);
+ QSTRING_S *qs_normal_part(QSTRING_S *);
+ QSTRING_S *qs_remove_trailing_spaces(QSTRING_S *);
+ QSTRING_S *trim_qs_from_cl(QSTRING_S *, QSTRING_S *, QSTRING_S *);
+ QSTRING_S *fix_qstring(QSTRING_S *, QSTRING_S *, QSTRING_S *);
+ QSTRING_S *fix_qstring_allowed(QSTRING_S *, QSTRING_S *, QSTRING_S *);
+ QSTRING_S *qs_add(char **, char *, QStrType, int, int, int, int);
+ QSTRING_S *remove_qsword(QSTRING_S *);
+ QSTRING_S *do_raw_quote_match(char **, char *, char *, char *, QSTRING_S **, QSTRING_S **);
+ void	 free_qs(QSTRING_S **);
+ int      word_is_prog(char *);
+ int      qstring_is_normal(QSTRING_S *);
+ int      exists_good_part(QSTRING_S *);
+ int      strcmp_qs(char *, char *);
+ int      count_levels_qstring(QSTRING_S *);
+ int      same_qstring(QSTRING_S *, QSTRING_S *);
+ int	 isaword(char *,int ,int);
+ int	 isamailbox(char *,int ,int);
+ int	 double_check_qstr(char *);
+ 
+ int
+ word_is_prog(char *word)
+ {
+   static char *list1[] = {"#include",
+ 			"#define",
+ 			"#ifdef",
+ 			"#ifndef",
+ 			"#elif",
+ 			"#if",
+ 			NULL};
+   static char *list2[] = {"#else",
+ 			"#endif",
+ 			 NULL};
+   int i, j = strlen(word), k, rv = 0;
+ 
+   for(i = 0; rv == 0 && list1[i] && (k = strlen(list1[i])) && k < j; i++)
+      if(!strncmp(list1[i], word, k) && ISspace(word[k]))
+        rv++;
+ 
+   if(rv)
+      return rv;
+ 
+    for(i = 0; rv == 0 && list2[i] && (k = strlen(list2[i])) && k <= j; i++)
+      if(!strncmp(list2[i], word, k) && (!word[k] || ISspace(word[k])))
+        rv++;
+ 
+    return rv;
+ }
+ 
+ /*
+  * This function creates a qstring pointer with the information that
+  * is_quote handles to it.
+  * Parameters:
+  * qs         - User supplied quote string
+  * word       - The line of text that the user is trying to read/justify
+  * beginw     - Where we need to start copying from
+  * endw       - Where we end copying
+  * offset     - Any offset in endw that we need to account for
+  * typeqs     - type of the string to be created
+  * neednext   - boolean, indicating if we need to compute the next field
+  *              of leave it NULL 
+  * 
+  * It is a mistake to call this function if beginw >= endw + offset.
+  * Please note the equality sign in the above inequality (this is because
+  * we always assume that qstring->value != "").
+  */ 
+ QSTRING_S *
+ qs_add(char **qs, char word[NSTRING], QStrType typeqs, int beginw, int endw, 
+ 	int offset, int neednext)
+ {
+     QSTRING_S *qstring, *nextqs;
+     int i;
+  
+     qstring = (QSTRING_S *) malloc (sizeof(QSTRING_S));
+     memset (qstring, 0, sizeof(QSTRING_S));
+     qstring->qstype = qsNormal;
+ 
+     if (beginw == 0){
+ 	beginw = endw + offset;
+ 	qstring->qstype = typeqs;
+     }
+ 
+     nextqs = neednext ? is_quote(qs, word+beginw, 1) : NULL;
+ 
+     qstring->value = (char *) malloc((beginw+1)*sizeof(char));
+     strncpy(qstring->value, word, beginw);
+     qstring->value[beginw] = '\0';
+ 
+     qstring->next = nextqs;
+ 
+     return qstring;
+ }
+ 
+ int
+ qstring_is_normal(QSTRING_S *cl)
+ { 
+    for (;cl && (cl->qstype == qsNormal); cl = cl->next);
+    return cl ? 0 : 1;
+ }
+ 
+ /*
+  * Given a quote string, this function returns the part that is the leading
+  * normal part of it. (the normal part is the part that is tagged qsNormal,
+  * that is to say, the one that is not controversial at all (like qsString
+  * for example).
+  */
+ QSTRING_S *
+ qs_normal_part(QSTRING_S *cl)
+ {
+ 
+   if (!cl)            /* nothing in, nothing out */
+      return cl;
+ 
+   if (cl->qstype != qsNormal)
+      free_qs(&cl);
+ 
+   if (cl)
+      cl->next = qs_normal_part(cl->next);
+ 
+   return cl;
+ }
+ 
+ /*
+  * this function removes trailing spaces from a quote string, but leaves the
+  * last one if there are trailing spaces
+  */ 
+ QSTRING_S *
+ qs_remove_trailing_spaces(QSTRING_S *cl)
+ {
+   QSTRING_S *rl = cl;
+   if (!cl)            /* nothing in, nothing out */
+      return cl;
+ 
+   if (cl->next)
+      cl->next = qs_remove_trailing_spaces(cl->next);
+   else{
+     if (value_is_space(cl->value))
+        free_qs(&cl);
+     else{
+        int i, l;
+        i = l = strlen(cl->value) - 1;
+        while (cl->value && cl->value[i]
+         && ISspace(cl->value[i]))
+            i--;
+         i += (i < l) ? 2 : 1;
+         cl->value[i] = '\0';
+     }
+   }
+   return cl;
+ }
+ 
+ /*
+  * This function returns if two strings are the same quote string.
+  * The call is not symmetric. cl must preceed the line nl. This function
+  * should be called for comparing the last part of cl and nl.
+  */
+ int
+ strcmp_qs(char *valuecl, char *valuenl)
+ {
+    int j;
+ 
+    for (j = 0; valuecl[j] && (valuecl[j] == valuenl[j]); j++);
+    return !strcmp(valuecl, valuenl)
+ 	 || (valuenl[j] && value_is_space(valuenl+j)
+ 			&& value_is_space(valuecl+j)
+ 			&& strlenis(valuecl+j) >= strlenis(valuenl+j))
+ 	 || (!valuenl[j] && value_is_space(valuecl+j));
+ }
+ 
+ int
+ count_levels_qstring(QSTRING_S *cl)
+ {
+   int count;
+   for (count = 0; cl ; count++, cl = cl->next);
+ 
+   return count;
+ }
+ 
+ int
+ value_is_space(char *value)
+ {
+   for (; value && *value && ISspace(*value); value++);
+ 
+   return value && *value ? 0 : 1;
+ }
+ 
+ void
+ free_qs(QSTRING_S **cl)
+ {
+   if (!(*cl))
+     return;
+ 
+   if ((*cl)->next)
+     free_qs(&((*cl)->next));
+ 
+   (*cl)->next = (QSTRING_S *) NULL;
+ 
+   if ((*cl)->value)
+      free((void *)(*cl)->value);
+    (*cl)->value = (char *) NULL;
+    free((void *)(*cl));
+    *cl = (QSTRING_S *) NULL;
+ }
+ 
+ /*
+  * This function returns the number of agreements between
+  * cl and nl. The call is not symmetric. cl must be the line
+  * preceding nl.
+  */
+ int
+ same_qstring(QSTRING_S *cl, QSTRING_S *nl)
+ {
+    int same = 0, done = 0;
+ 
+    for (;cl && nl && !done; cl = cl->next, nl = nl->next)
+        if (cl->qstype == nl->qstype
+          && (!strcmp(cl->value, nl->value)
+            || (!cl->next && strcmp_qs(cl->value, nl->value))))
+ 	same++;
+       else
+ 	done++;
+    return same;
+ }
+ 
+ QSTRING_S *
+ trim_qs_from_cl(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl)
+ {
+     QSTRING_S *cqstring = pl ? pl : nl;
+     QSTRING_S *tl = pl ? pl : nl;
+     int p, c;
+ 
+     if (qstring_is_normal(tl))
+ 	return tl;
+ 
+     p = same_qstring(pl ? pl : cl, pl ? cl : nl);
+ 
+     for (c = 1; c < p; c++, cl = cl->next, tl = tl->next);
+ 
+     /*
+      * cl->next and tl->next differ, it may be because cl->next does not
+      * exist or tl->next does not exist or simply both exist but are
+      * different. In this last case, it may be that cl->next->value is made
+      * of spaces. If this is the case, tl advances once more.
+      */
+ 
+     if (tl->next){
+ 	if (cl && cl->next && value_is_space(cl->next->value))
+ 	   tl = tl->next;
+ 	if (tl->next)
+ 	   free_qs(&(tl->next));
+     }
+ 
+     if (!p)
+        free_qs(&cqstring);
+ 
+     return cqstring;
+ }
+ 
+ /* This function trims cl so that it returns a real quote string based
+  * on information gathered from the previous and next lines. pl and cl are
+  * also trimmed, but that is done in another function, not here.
+  */
+ QSTRING_S *
+ fix_qstring(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl)
+ {
+    QSTRING_S *cqstring = cl, *nqstring = nl, *pqstring = pl;
+    int c, n;
+ 
+    if (qstring_is_normal(cl))
+      return cl;
+ 
+    c = count_levels_qstring(cl);
+    n = same_qstring(cl,nl);
+ 
+    /* by the definition of n and c above we have n <= c */
+ 
+    if (!n){  /* no next line or no agreement with next line */
+       int p = same_qstring(pl, cl); /* number of agreements between pl and cl */
+       QSTRING_S *tl;              /* test line */
+ 
+       /*
+        * Here p <= c, so either p < c or p == c. If p == c, we are done,
+        * and return cl. If not, there are two cases, either p == 0 or
+        * 0 < p < c. In the first case, we do not have enough evidence
+        * to return anything other than the normal part of cl, in the second
+        * case we can only return p levels of cl.
+        */
+ 
+       if (p == c)
+ 	tl = cqstring;
+       else{
+          if (p){
+ 	   for (c = 1; c < p; c++)
+ 	      cl = cl->next;
+ 	   free_qs(&(cl->next));
+ 	   tl = cqstring;
+          }
+          else{
+ 	   int done = 0;
+ 	   QSTRING_S *al = cl;  /* another line */ 
+ 	/*
+ 	 * Ok, we really don't have enough evidence to return anything,
+ 	 * different from the normal part of cl, but it could be possible
+ 	 * that we may want to accept the not-normal part, so we better
+ 	 * make an extra test to determine what needs to be freed
+ 	 */
+ 	   while (pl && cl && cl->qstype == pl->qstype
+ 		    && !strucmp(cl->value, pl->value)){
+ 		cl = cl->next;
+ 		pl = pl->next;
+ 	   }
+            if (pl && cl && cl->qstype == pl->qstype
+                        && strcmp_qs(pl->value, cl->value))
+                cl = cl->next;  /* next level differs only in spaces */
+            while (!done){
+                while (cl && cl->qstype == qsNormal)
+                    cl = cl->next;
+                if (cl){
+                   if ((cl->qstype == qsString)
+                       && (cl->value[strlen(cl->value) - 1] == '>'))
+                      cl = cl->next;
+                   else done++;
+                }
+                else done++;
+            }
+            if (al == cl){
+              free_qs(&(cl));
+              tl = cl;
+            }
+            else {
+              while (al && (al->next != cl))
+                 al = al->next;
+              cl = al;
+              if (cl && cl->next)
+                 free_qs(&(cl->next));
+              tl = cqstring;
+            }
+          }
+       }
+       return tl;
+    }
+ 
+    /* so n <= c (see above) hence n < c or n = c. If n < c then n+1 <= c, so we 
+     * either have n == c or n + 1 < c or n + 1 == c. We analyze each case in the
+     * following code
+     */
+ 
+    if (n + 1 < c){  /* if there are not enough agreements */
+       int p = same_qstring(pl, cl); /* number of agreement between pl and cl */
+       QSTRING_S *tl; /* test line */
+        /*
+         * There's no way we can use cl in this case, but we can use
+         * part of cl, this is if pl does not have more agreements
+         * with cl.
+         */ 
+       if (p == c)
+        tl = cqstring;
+       else{
+        int m = p < n ? n : p;
+        for (c = 1; c < m; c++){
+          pl = pl ? pl->next : (QSTRING_S *) NULL;
+          nl = nl ? nl->next : (QSTRING_S *) NULL;
+          cl = cl->next;
+        }
+        if (p == n && pl && pl->next && nl && nl->next
+           && ((cl->next->qstype == pl->next->qstype)
+              || (cl->next->qstype == nl->next->qstype))
+           && (strcmp_qs(cl->next->value, pl->next->value)
+              || strcmp_qs(pl->next->value, cl->next->value)
+              || strcmp_qs(cl->next->value, nl->next->value)
+              || strcmp_qs(nl->next->value, cl->next->value)))
+          cl = cl->next;        /* next level differs only in spaces */
+        if (cl->next)
+           free_qs(&(cl->next));
+        tl = cqstring;
+       }
+       return tl;
+    }
+    if (n + 1 == c){
+       int p = same_qstring(pl, cl);
+       QSTRING_S *tl; /* test line */
+ 
+       /*
+        * p <= c, so p <= n+1, which means p < n + 1 or p == n + 1.
+        * If p < n + 1, then p <= n.
+        * so we have three possibilities:
+        *       p == n + 1 or p == n or p < n.
+        * In the first case we copy p == n + 1 == c levels, in the second
+        * and third case we copy n levels, and check if we can copy the
+        * n + 1 == c level.
+        */
+       if (p == n + 1)      /* p == c, in the above sense of c */
+        tl = cl;          /* use cl, this is enough evidence */
+       else{
+        for (c = 1; c < n; c++)
+          cl = cl->next;
+        /*
+         * Here c == n, we only have one more level of cl, and at least one
+         * more level of nl
+         */
+        if (cl->next->qstype == qsNormal)
+           cl = cl->next;
+        if (cl->next)
+           free_qs(&(cl->next));
+        tl = cqstring;
+       }
+       return tl;
+    }
+    /* else n == c, Yeah!!! */
+    return cqstring;
+ }
+ 
+ QSTRING_S *
+ fix_qstring_allowed(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl)
+ {
+   if(!cl)
+     return (QSTRING_S *) NULL;
+ 
+   if (qs_allowed(cl))
+       cl->next = fix_qstring_allowed(cl->next, (nl ? nl->next : NULL),
+                        (pl ? pl->next : NULL));
+   else
+      if((nl && cl->qstype == nl->qstype) || (pl && cl->qstype == pl->qstype)
+       || (!nl && !pl))
+       free_qs(&cl);
+   return cl;
+ }
+ 
+ /*
+  * This function flattens the quote string returned to us by is_quote. A
+  * crash in this function implies a bug elsewhere.
+  */
+ void
+ flatten_qstring(QSTRING_S *qs, char *buff, int bufflen)
+ { 
+    int i, j; 
+    if(!buff || bufflen <= 0)
+      return;
+ 
+    for (i = 0; qs; qs = qs->next)
+      for (j = 0; i < bufflen - 1
+                &&  (qs->value[j]) && (buff[i++] = qs->value[j]); j++);
+   buff[i] = '\0';
+ }
+ 
+ extern int list_len;
+ 
+ 
+ int
+ double_check_qstr(char *q)
+ {
+   if(!q || !*q)
+     return 0;
+ 
+   return (*q == '#') ? 1 : 0;
+ }
+ 
+ /*
+  * Given a string, we return the position where the function thinks that
+  * the quote string is over, if you are ever thinking of fixing something,
+  * you got to the right place. Memory freed by caller. Experience shows
+  * that it only makes sense to initialize memory when we need it, not at
+  * the start of this function.
+  */
+ QSTRING_S *
+ is_quote (char **qs,char *word, int been_here)
+ {
+    int i = 0, j, nxt, prev, finished = 0, offset;
+    unsigned char c;
+    QSTRING_S *qstring = (QSTRING_S *) NULL;
+ 
+    if (word == NULL || word[0] == '\0')
+       return (QSTRING_S *) NULL;
+ 
+    while (!finished){
+        /*
+         * Before we apply our rules, let's advance past the quote string
+         * given by the user, this will avoid not recognition of the
+         * user's indent string and application of the arbitrary rules
+         * below. Notice that this step may bring bugs into this
+         * procedure, but these bugs will only appear if the indent string
+         * is really really strange and the text to be justified
+         * cooperates a lot too, so in general this will not be a problem.
+         * If you are concerned about this bug, simply remove the
+         * following lines after this comment and before the "switch"
+         * command below and use a more normal quote string!.
+         */
+        for(j = 0; j < list_len; j++){
+ 	  if(!double_check_qstr(qs[j])){
+ 	    i += advance_quote_string(qs[j], word, i);
+ 	    if (!word[i]) /* went too far? */
+ 	      return qs_add(qs, word, qsNormal, 0, i, 0, 0);
+ 	  }
+ 	  else
+ 	    break;
+        }
+ 
+       switch (c = (unsigned char) now(word,i)){
+        case NBSP:
+        case TAB :
+        case ' ' : { QSTRING_S *nextqs, *d;
+ 
+                    for (; ISspace(word[i]); i++); /* FIX ME */
+                    nextqs = is_quote(qs,word+i, 1);
+                  /*
+                   * Merge qstring and nextqs, since this is an artificial
+                   * separation, unless nextqs is of different type.
+                   * What this means in practice is that if
+                   * qs->qstype == qsNormal and qs->next != NULL, then
+                   * qs->next->qstype != qsNormal.
+                   *
+                   * Can't use qs_add to merge because it could lead
+                   * to an infinite loop (e.g a line "^ ^").
+                   */
+                    i += nextqs && nextqs->qstype == qsNormal
+                        ? strlen(nextqs->value) : 0;
+                    qstring = (QSTRING_S *) malloc (sizeof(QSTRING_S));
+                    memset (qstring, 0, sizeof(QSTRING_S));
+                    qstring->value = (char *) malloc((i+1)*sizeof(char));
+                    strncpy(qstring->value, word, i);
+                    qstring->value[i] = '\0';
+                    qstring->qstype   = qsNormal;
+ 		   if(nextqs && nextqs->qstype == qsNormal){
+ 			d = nextqs->next;
+ 			nextqs->next = NULL;
+ 			qstring->next = d;
+ 			free_qs(&nextqs);
+ 		   }
+ 		   else
+ 		     qstring->next     = nextqs;
+ 
+ 		   return qstring;
+ 		 }
+                 break;
+        case RPAREN:            /* parenthesis ')' */
+                     if ((i != 0) || ((i == 0) && been_here))
+                        i++;
+                     else
+                        if (i == 0)
+                           return qs_add(qs, word, qsChar, i, i, 1, 1);
+                        else
+                           finished++;
+                    break;
+ 
+        case ':':                       /* colon */
+        case '~': nxt = next(word,i);
+                  if ((is_tilde(c) && (nxt == '/'))
+                        || (is_colon(c) && !is_cquote(nxt)
+                                        && !is_cword(nxt) && nxt != RPAREN))
+                     finished++;
+                  else if (is_cquote(c)
+                        || is_cquote(nxt)
+                        || (c != '~' && nxt == RPAREN)
+                        || (i != 0 && ISspace(nxt))
+                        || is_cquote(prev = before(word,i))
+                        || (ISspace(prev) && !is_tilde(c))
+                        || (is_tilde(c) && nxt != '/'))
+                      i++;
+                  else if (i == 0 && been_here)
+                       return qs_add(qs, word, qsChar, i, i, 1, 1);
+                  else
+                       finished++;
+                 break;
+ 
+        case '<' :
+        case '=' :
+        case '-' : offset = is_cquote(nxt = next(word,i)) ? 2
+                             : (nxt == c && is_cquote(next(word,i+1))) ? 3 : -1;
+ 
+                   if (offset > 0)
+                       return qs_add(qs, word, qsString, i, i, offset, 1);
+                   else
+                       finished++;
+                 break;
+ 
+        case '[' :
+        case '+' :      /* accept +>, *> */
+        case '*' :  if (is_rarrow(nxt = next(word, i)) || /* stars */
+                      (ISspace(nxt) && is_rarrow(next(word,i+1))))
+                         i++;
+                    else
+                       finished++;
+                 break;
+ 
+        case '^' :
+        case '!' :
+        case '%' : if (next(word,i) != c)
+                      return qs_add(qs, word, qsChar, i, i+1, 0, 1);
+                   else
+                      finished++;
+                 break;
+ 
+        case '_' : if(ISspace(next(word, i)))
+                        return qs_add(qs, word, qsChar, i, i+1, 0, 1);
+                   else
+                      finished++;
+                   break;
+ 
+        case '#' : { QStrType qstype = qsChar;
+                     if((nxt = next(word, i)) != c){
+                        if(isdigit((int) nxt))
+                          qstype = qsGdb;
+                        else
+                          if(word_is_prog(word))
+                             qstype = qsProg;
+                        return qs_add(qs, word, qstype, i, i+1, 0, 1);
+                     }
+                     else
+                        finished++;
+                     break;
+                   }
+ 
+          default:
+            if (is_cquote(c))
+               i++;
+            else if (is_cletter(c)){
+                for (j = i; (is_cletter(nxt = next(word,j)) || is_cnumber(nxt))
+                            && !(ISspace(nxt));j++);
+                  /*
+                   * The whole reason why we are splitting the quote
+                   * string is so that we will be able to accept quote
+                   * strings that are strange in some way. Here we got to
+                   * a point in which a quote string might exist, but it
+                   * could be strange, so we need to create a "next" field
+                   * for the quote string to warn us that something
+                   * strange is coming. We need to confirm if this is a
+                   * good choice later. For now we will let it pass.
+                   */
+                if (isaword(word,i,j) || isamailbox(word,i,j)){
+                    int offset;
+                    QStrType qstype;
+ 
+                    offset = (is_cquote(c = next(word,j))
+                             || (c == RPAREN)) ? 2
+                                : ((ISspace(c)
+                                     && is_cquote(next(word,j+1))) ? 3 : -1);
+ 
+                    qstype = (is_cquote(c) || (c == RPAREN))
+                      ? (is_qsword(c) ? qsWord : qsString)
+                      : ((ISspace(c) && is_cquote(next(word,j+1)))
+                         ? (is_qsword(next(word,j+1))
+                            ? qsWord : qsString)
+                                  : qsString);
+ 
+                    /*
+                     * qsWords are valid quote strings only when
+                     * they are followed by text.
+                     */
+                    if (offset > 0 && qstype == qsWord &&
+                        !allwd_after_qsword(now(word,j + offset)))
+                        offset = -1;
+ 
+                    if (offset > 0)
+                        return qs_add(qs, word, qstype, i, j, offset, 1);
+                  }
+                  finished++;
+                }
+                else{
+ 	           if(i > 0)
+ 		     return qs_add(qs, word, qsNormal, 0, i, 0, 1);
+ 	           else if(!forbidden(c))
+                      return qs_add(qs, word, qsChar, 0, 1, 0, 1);
+                    else    /* chao pescao */
+                      finished++;
+                }
+        break;
+       }  /* End Switch */
+     }  /* End while */
+     if (i > 0)
+        qstring = qs_add(qs, word, qsNormal, 0, i, 0, 0);
+     return qstring;
+ }
+ 
+ int
+ isaword(char word[NSTRING], int i, int j)
+ {
+   return (i <= j && is_cletter(word[i])) ?
+           (i < j ? isaword(word,i+1,j) : 1) : 0;
+ }
+ 
+ int
+ isamailbox(char word[NSTRING], int i, int j)
+ {
+   return i <= j && (is_cletter(word[i]) || is_a_digit(word[i])
+                  || word[i] == '.')
+        ? (i < j ? isamailbox(word,i+1,j) : 1) : 0;
+ }
+ 
+ /*
+    This routine removes the last part that is qsword or qschar that is not
+    followed by a normal part. This means that if a qsword or qschar is
+    followed by a qsnormal (or qsstring), we accept the qsword (or qschar)
+    as part of a quote string.
+  */
+ QSTRING_S *
+ remove_qsword(QSTRING_S *cl)
+ {
+      QSTRING_S *np = cl;
+      QSTRING_S *cp = np;               /* this variable trails cl */
+ 
+      while(1){
+         while (cl && cl->qstype == qsNormal)
+             cl = cl->next;
+ 
+         if (cl){
+          if (((cl->qstype == qsWord) || (cl->qstype == qsChar))
+                && !exists_good_part(cl)){
+              if (np == cl)     /* qsword or qschar at the beginning */
+                 free_qs(&cp);
+              else{
+                 while (np->next != cl)
+                     np = np->next;
+                 free_qs(&(np->next));
+             }
+             break;
+          }
+          else
+             cl = cl->next;
+         }
+         else
+          break;
+     }
+     return cp;
+ }
+ 
+ int
+ exists_good_part (QSTRING_S *cl)
+ {
+    return (cl ? (((cl->qstype != qsWord) && (cl->qstype != qsChar)
+                  && qs_allowed(cl) && !value_is_space(cl->value))
+               ? 1 : exists_good_part(cl->next))
+ 	      : 0);
+ }
+ 
+ int
+ line_isblank(char **q, char *GLine, char *NLine, char *PLine, int buflen)
+ {
+     int n = 0;
+     QSTRING_S *cl;
+     char qstr[NSTRING];
+ 
+     cl = do_raw_quote_match(q, GLine, NLine, PLine, NULL, NULL);
+ 
+     flatten_qstring(cl, qstr, NSTRING);
+ 
+     free_qs(&cl);
+ 
+     for(n = strlen(qstr); n < buflen && GLine[n]; n++)
+        if(!ISspace((unsigned char) GLine[n]))
+          return(FALSE);
+ 
+     return(TRUE);
+ }
+ 
+ QSTRING_S *
+ do_raw_quote_match(char **q, char *GLine, char *NLine, char *PLine, QSTRING_S **nlp, QSTRING_S **plp)
+ {
+    QSTRING_S *cl, *nl = NULL, *pl = NULL;
+    char nbuf[NSTRING], pbuf[NSTRING], buf[NSTRING];
+    int emptypl = 0, emptynl = 0;
+ 
+    if (!(cl = is_quote(q, GLine, 0)))  /* if nothing in, nothing out */
+       return cl;
+ 
+    nl = is_quote(q, NLine, 0);         /* Next Line     */
+    if (nlp) *nlp = nl;
+    pl = is_quote(q, PLine, 0);         /* Previous Line */
+    if (plp) *plp = pl;
+    /*
+     * If there's nothing in the preceeding or following line
+     * there is not enough information to accept it or discard it. In this
+     * case it's likely to be an isolated line, so we better accept it
+     * if it does not look like a word.
+     */ 
+    flatten_qstring(pl, pbuf, NSTRING);
+    emptypl = (!PLine || !PLine[0] ||
+                (pl && value_is_space(pbuf) && !PLine[strlen(pbuf)])) ? 1 : 0;
+    if (emptypl){
+       flatten_qstring(nl, nbuf, NSTRING);
+       emptynl = (!NLine || !NLine[0] ||
+                (nl && value_is_space(nbuf) && !NLine[strlen(nbuf)])) ? 1 : 0;
+       if (emptynl){
+        cl = remove_qsword(cl);
+        if((cl = fix_qstring_allowed(cl, NULL, NULL)) != NULL)
+           cl = qs_remove_trailing_spaces(cl);
+        free_qs(&nl);
+        free_qs(&pl);
+        if(nlp) *nlp = NULL;
+        if(plp) *plp = NULL;
+ 
+        return cl;
+       }
+    }
+ 
+    /*
+     * If either cl, nl or pl contain suspicious characters that may make
+     * them (or not) be quote strings, we need to fix them, so that the
+     * next pass will be done correctly.
+     */
+ 
+    cl = fix_qstring(cl, nl, pl);
+    nl = trim_qs_from_cl(cl, nl, NULL);
+    pl = trim_qs_from_cl(cl, NULL, pl);
+    if((cl = fix_qstring_allowed(cl, nl, pl)) != NULL){
+      nl = trim_qs_from_cl(cl, nl, NULL);
+      pl = trim_qs_from_cl(cl, NULL, pl);
+    }
+    else{
+      free_qs(&nl);
+      free_qs(&pl);
+    }
+    if(nlp) 
+       *nlp = nl;
+    else
+      free_qs(&nl);
+    if(plp)
+      *plp = pl;
+    else
+      free_qs(&pl);
+    return cl;
+ }
+ 
+ QSTRING_S *
+ do_quote_match(char **q, char *GLine, char *NLine, char *PLine, char *rqstr, 
+ int rqstrlen, int plb)
+ {
+     QSTRING_S *cl, *nl = NULL, *pl = NULL;
+     int c, n, p,i, j, NewP, NewC, NewN, clength, same = 0;
+     char nbuf[NSTRING], pbuf[NSTRING], buf[NSTRING];
+ 
+     if(rqstr)
+       *rqstr = '\0';
+ 
+     /* if nothing in, nothing out */
+     cl = do_raw_quote_match(q, GLine, NLine, PLine, &nl, &pl);
+     if(cl == NULL){
+       free_qs(&nl);
+       free_qs(&pl);
+       return cl;
+     }
+ 
+     flatten_qstring(cl, rqstr, rqstrlen);
+     flatten_qstring(cl,  buf, NSTRING);
+     flatten_qstring(nl, nbuf, NSTRING);
+     flatten_qstring(pl, pbuf, NSTRING);
+ 
+     /*
+      * Once upon a time, is_quote used to return the length of the quote
+      * string that it had found. One day, not long ago, black hand came
+      * and changed all that, and made is_quote return a quote string
+      * divided in several fields, making the algorithm much more
+      * complicated. Fortunately black hand left a few comments in the
+      * source code to make it more understandable. Because of this change
+      * we need to compute the lengths of the quote strings separately
+      */
+      c =  buf &&  buf[0] ? strlen(buf)  : 0;
+      n = nbuf && nbuf[0] ? strlen(nbuf) : 0;
+      p = pbuf && pbuf[0] ? strlen(pbuf) : 0;
+      /*
+       * When quote strings contain only blank spaces (ascii code 32) the
+       * above count is equal to the length of the quote string, but if
+       * there are TABS, the length of the quote string as seen by the user
+       * is different than the number that was just computed.  Because of
+       * this we demand a recount (hmm.. unless you are in Florida, where
+       * recounts are forbidden)
+       */
+      NewP = strlenis(pbuf);
+      NewC = strlenis(buf);
+      NewN = strlenis(nbuf);
+ 
+      /*
+       * For paragraphs with spaces in the first line, but no space in the
+       * quote string of the second line, we make sure we choose the quote
+       * string without a space at the end of it.
+       */
+      if ((NLine && !NLine[0])
+        && ((PLine && !PLine[0])
+             || (((same = same_qstring(pl, cl)) != 0)
+                        && (same != count_levels_qstring(cl)))))
+        cl = qs_remove_trailing_spaces(cl);
+      else
+        if (NewC > NewN){
+           int agree = 0;
+           for (j = 0; (j < n) && (GLine[j] == NLine[j]); j++);
+           clength = j;
+        /* clength is the common length in which Gline and Nline agree */
+        /* j < n means that they do not agree fully */
+        /* GLine = "   \tText"
+           NLine = "   Text" */
+          if(j == n)
+             agree++;
+          if (clength < n){ /* see if buf and nbuf are padded with spaces and tabs */
+             for (i = clength; i < n && ISspace(NLine[i]); i++);
+             if (i == n){/* padded NLine until the end of spaces? */
+                for (i = clength; i < c && ISspace(GLine[i]); i++);
+                if (i == c) /* Padded CLine until the end of spaces? */
+                   agree++;
+             }
+          }
+          if (agree){
+ 	    for (j = clength; j < c && ISspace(GLine[j]); j++);
+ 	    if (j == c){
+       /*
+        * If we get here, it means that the current line has the same
+        * quote string (visually) than the next line, but both of them
+        * are padded with different amount of TABS or spaces at the end.
+        * The current line (GLine) has more spaces/TABs than the next
+        * line. This is the typical situation that is found at the
+        * begining of a paragraph. We need to check this, however, by
+        * checking the previous line. This avoids that we confuse
+        * ourselves with being in the last line of a paragraph.
+        * Example when it should not free_qs(cl)
+        * "    Text in Paragraph 1" (PLine)
+        * "    Text in Paragraph 1" (GLine)
+        * "  Other Paragraph Number 2" (NLine)
+        *
+        * Example when it should free_qs(cl):
+        * ":) "                (PLine) p = 3, j = 3
+        * ":)   Text"          (GLine) c = 5
+        * ":) More text"       (NLine) n = 3
+        *
+        * Example when it should free_qs(cl):
+        * ":) "                (PLine) p =  3, j = 3
+        * ":) > > >   Text"    (GLine) c = 11
+        * ":) > > > More text" (NLine) n =  9
+        *
+        * Example when it should free_qs(cl):
+        * ":) :) "             (PLine) p =  6, j = 3
+        * ":) > > >   Text"    (GLine) c = 11
+        * ":) > > > More text" (NLine) n =  9
+        *
+        * Example when it should free_qs(cl):
+        * ":) > > >     "      (PLine) p = 13, j = 11
+        * ":) > > >   Text"    (GLine) c = 11
+        * ":) > > > More text" (NLine) n =  9
+        *
+        * The following example is very interesting. The "Other Text"
+        * line below should free the quote string an make it equal to the
+        * quote string of the line below it, but any algorithm trying
+        * to advance past that line should make it stop there, so
+        * we need one more check, to check the raw quote string and the
+        * processed quote string at the same time.
+        * FREE qs in this example.
+        * "   Some Text"       (PLine) p = 3, j = 0
+        * "\tOther Text"       (GLine) c = 1
+        * "   More Text"       (NLine) n = 3
+        *
+        */
+               for (j = 0; (j < p) && (GLine[j] == PLine[j]); j++);
+ 	      if ((p != c || j != p) && NLine[n])
+ 		 if(!get_indent_raw_line(q, PLine, nbuf, NSTRING, p, plb)
+                    || NewP + strlenis(nbuf) != NewC){
+                    free_qs(&cl);
+                    free_qs(&pl);
+                    return nl;
+ 		 }
+             } 
+          }
+        }
+ 
+      free_qs(&nl);
+      free_qs(&pl);
+ 
+      return cl;
+ }
+ 
+ /*
+  * Given a line, an initial position, and a quote string, we advance the
+  * current line past the quote string, including arbitraty spaces
+  * contained in the line, except that it removes trailing spaces. We do
+  * not handle TABs, if any, contained in the quote string. At least not
+  * yet.
+  *
+  * Arguments: q - quote string
+  *          l - a line to process
+  *          i - position in the line to start processing. i = 0 is the
+  *              begining of that line.
+  */
+ int
+ advance_quote_string(char *q, char l[NSTRING], int i)
+ {
+     int n = 0, j = 0, is = 0, es = 0;
+     int k, m, p, adv;
+     char qs[NSTRING] = {'\0'};
+     if(!q || !*q)
+       return(0);
+     for (p = strlen(q); (p > 0) && (q[p - 1] == ' '); p--, es++);
+     if (!p){  /* string contains only spaces */
+        for (k = 0; ISspace(l[i + k]); k++);
+        k -= k % es;
+        return k;
+     }
+     for (is = 0; ISspace(q[is]); is++); /* count initial spaces */ 
+     for (m = 0 ; is + m < p ; m++)
+       qs[m] = q[is + m];   /* qs = quote string without any space at the end */
+                      /* advance as many spaces as there are at the begining */
+     for (k = 0; ISspace(l[i + j]); k++, j++);
+                       /* now find the visible string in the line */
+     for (m = 0; qs[m] && l[i + j] == qs[m]; m++, j++);
+     if (!qs[m]){      /* no match */
+       /*
+        * So far we have advanced at least "is" spaces, plus the visible
+        * string "qs". Now we need to advance the trailing number of
+        * spaces "es". If we can do that, we have found the quote string.
+        */
+       for (p = 0; ISspace(l[i + j + p]); p++);
+       adv = advance_quote_string(q, l, i + j + ((p < es) ? p : es));
+       n = ((p < es) ? 0 : es) + k + m + adv;
+     }
+     return n;
+ }
+ 
+ /*
+  * This function returns the effective length in screen of the quote
+  * string. If the string contains a TAB character, it is added here, if
+  * not, the length returned is the length of the string
+  */
+ int strlenis(char *qstr)
+ {
+   int i, rv = 0; 
+   for (i = 0; qstr && qstr[i]; i++)
+        rv += ((qstr[i] == TAB) ? (~rv & 0x07) + 1 : 1);
+   return rv;
+ }
+ 
+ int
+ is_indent (char word[NSTRING], int plb)
+ {
+   int i = 0, finished = 0, j, k, digit = 0, bdigits = -1, alpha = 0;
+   unsigned char c, nxt;
+ 
+    if (!word || !word[0])
+       return i;
+ 
+    for (i = 0, j = 0; ISspace(word[i]); i++, j++);
+    while ((i < NSTRING - 2) && !finished){
+       switch (c = (unsigned char) now(word,i)){
+           case NBSP:
+           case TAB :
+           case ' ' : for (; ISspace(word[i]); i++)
+ 			;
+                      if (!is_indent_char(now(word,i)))
+                        finished++;
+                   break;
+ 
+            case '+' :
+            case '.' :
+            case ']' :
+            case '*' :
+            case '}' :
+            case '-' :
+            case RPAREN:
+                   nxt = (unsigned char) next(word,i);
+                   if ((c == '.' && allowed_after_period(nxt) && alpha)
+                        || (c == '*' && allowed_after_star(nxt))
+                        || (c == '}' && allowed_after_braces(nxt))
+                        || (c == '-' && allowed_after_dash(nxt))
+                        || (c == '+' && allowed_after_dash(nxt))
+                        || (c == RPAREN && allowed_after_parenth(nxt))
+                        || (c == ']' && allowed_after_parenth(nxt)))
+                      i++;
+                   else
+                      finished++;
+                   break;
+ 
+            case 0xE2:          /* bullet? */
+ 		  nxt = (unsigned char) next(word,i);
+ 		  if(nxt == 0x80){
+ 		     nxt = (unsigned char) next(word,i+1);
+ 		     if(nxt == 0xA2){
+ 			nxt = (unsigned char) next(word,i+2);
+ 			if(allowed_after_star(nxt))
+ 			  i += 3;
+ 			else
+ 			  finished++;
+ 		     }
+ 		     else finished++;
+ 		  }
+ 		  else finished++;
+                   break;
+ 
+             default : if (is_a_digit(c) && plb){
+                         if (bdigits < 0)
+                            bdigits = i;  /* first digit */
+                         for (k = i; is_a_digit(now(word,k)); k++);
+                         if (k - bdigits > 2){ /* more than 2 digits? */
+                            i = bdigits; /* too many! */
+                            finished++;
+                         }
+                         else{
+                            if(allowed_after_digit(now(word,k),word,k)){
+                              alpha++;
+                              i = k;
+                            }
+                            else{
+                              i = bdigits;
+                              finished++;
+                            }
+                         }
+                       }
+                       else
+                         finished++;
+                    break;
+ 
+       }
+    }
+    if (i == j)
+       i = 0;  /* there must be something more than spaces in an indent string */
+    return i;
+ }
+ 
+ int
+ get_indent_raw_line(char **q, char *GLine, char *buf, int buflen, int k, int plb)
+ {
+      int i, j;
+      char testline[1024];
+ 
+      if(k > 0){
+ 	for(j = 0; GLine[j] != '\0'; j++){
+ 	   testline[j] = GLine[j];
+ 	   testline[j+1] = '\0';
+ 	   if(strlenis(testline) >= strlenis(buf))
+ 	     break;
+ 	}
+ 	k = ++j;     /* reset k */
+      }
+      i = is_indent(GLine+k, plb);
+ 
+      for (j = 0; j < i && j < buflen && (buf[j] = GLine[j + k]); j++);
+      buf[j] = '\0';
+ 
+      return i;
+ }
+ 
+ /* support for remembering quote strings across messages */
+ char **allowed_qstr = NULL;
+ int list_len = 0;
+ 
+ void
+ free_allowed_qstr(void)
+ {
+   int i;
+   char **q = allowed_qstr;
+ 
+   if(q == NULL)
+     return;
+ 
+   for(i = 0; i < list_len; i++)
+     fs_give((void **)&q[i]);
+ 
+   fs_give((void **)q);
+   list_len = 0;
+ }
+ 
+ void
+ add_allowed_qstr(void *q, int type)
+ {
+   int i;
+ 
+   if(allowed_qstr == NULL){
+      allowed_qstr =  malloc(sizeof(char *));
+      list_len = 0;
+   }
+ 
+   if(type == 0){
+     allowed_qstr[list_len] = malloc((1+strlen((char *)q))*sizeof(char));
+     strcpy(allowed_qstr[list_len], (char *)q);
+   }
+   else
+     allowed_qstr[list_len] = (char *) ucs4_to_utf8_cpystr((UCS *)q);
+ 
+   fs_resize((void **)&allowed_qstr, (++list_len + 1)*sizeof(char *));
+   allowed_qstr[list_len] = NULL;
+ }
+ 
+ void
+ record_quote_string (QSTRING_S *qs)
+ {
+   int i, j, k;
+ 
+   for(; qs && qs->value; qs = qs->next){
+     j = 0;
+     for (; ;){
+        k = j;
+        for(i = 0; i < list_len; i++){
+           j += advance_quote_string(allowed_qstr[i], qs->value, j);
+           for(; ISspace(qs->value[j]); j++);
+        }
+        if(k == j)
+ 	 break;
+     }
+     if(qs->value[j] != '\0')
+ 	add_allowed_qstr((void *)(qs->value + j), 0);
+   }
+ }
+ 
+ /* type utf8: code 0; ucs4: code 1. */
+ char **
+ default_qstr(void *q, int type)
+ {
+   if(allowed_qstr == NULL)
+     add_allowed_qstr(q, type);
+ 
+   return allowed_qstr;
+ }
+ 
diff -rc alpine-2.26/pith/osdep/color.h alpine-2.26.fillpara/pith/osdep/color.h
*** alpine-2.26/pith/osdep/color.h	2022-06-02 18:14:00.495274738 -0600
--- alpine-2.26.fillpara/pith/osdep/color.h	2022-06-02 18:14:57.039136093 -0600
***************
*** 17,22 ****
--- 17,40 ----
  #ifndef PITH_OSDEP_COLOR_INCLUDED
  #define PITH_OSDEP_COLOR_INCLUDED
  
+ /*
+  * struct that will help us determine what the quote string of a line
+  * is. The "next" field indicates the presence of a possible continuation.
+  * The idea is that if a continuation fails, we free it and check for the
+  * remaining structure left
+  */
+ 
+ typedef enum {qsNormal, qsString, qsWord, qsChar, qsGdb, qsProg, qsText} QStrType;
+ 
+ typedef struct QSTRING {
+ 	char		*value;		/* possible quote string */
+ 	QStrType	 qstype;	/* type of quote string  */
+ 	struct QSTRING	*next;		/* possible continuation */
+ } QSTRING_S;
+ 
+ #define UCH(c) ((unsigned char) (c))
+ #define NBSP UCH('\240')
+ #define ISspace(c) (UCH(c) == ' ' || UCH(c) == TAB || UCH(c) == NBSP)
  
  #define RGBLEN 11
  #define MAXCOLORLEN 11			/* longest string a color can be */
***************
*** 93,98 ****
  char	   *pico_get_last_bg_color(void);
  char	   *color_to_canonical_name(char *);
  int	    pico_count_in_color_table(void);
! 
  
  #endif /* PITH_OSDEP_COLOR_INCLUDED */
--- 111,124 ----
  char	   *pico_get_last_bg_color(void);
  char	   *color_to_canonical_name(char *);
  int	    pico_count_in_color_table(void);
! int 	    is_indent(char *, int);
! int	    get_indent_raw_line (char **, char *, char *, int, int, int);
! int  	    line_isblank(char **, char *, char *, char *, int);
! int	    strlenis(char *);
! int	    value_is_space(char *);
! int	    advance_quote_string(char *, char *, int);
! void	    free_allowed_qstr(void);
! void	    add_allowed_qstr(void *, int);
! void	    record_quote_string (QSTRING_S *);
  
  #endif /* PITH_OSDEP_COLOR_INCLUDED */
diff -rc alpine-2.26/pith/pine.hlp alpine-2.26.fillpara/pith/pine.hlp
*** alpine-2.26/pith/pine.hlp	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pith/pine.hlp	2022-06-02 18:14:57.059136043 -0600
***************
*** 8510,8515 ****
--- 8510,8555 ----
  &quot;type the character ^&quot;.
  
  <P>
+ This version of Alpine contains an enhanced algorithm for justification,
+ which allows you to justify text that contains more complicated quote
+ strings. This algorithm is based on pragmatics, rather than on a theory,
+ and seems to work well with most messages. Below you will find technical
+ information on how this algorithm works.
+ 
+ <P>
+ When justifying, Alpine goes through each line of the text and tries to
+ determine for each line what the quote string of that line is. The quote
+ string you provided is always recognized. Among other characters 
+ recognized is &quot;&gt;&quot;.
+ 
+ <P>
+ Some other constructions of quote strings are recognized only if they
+ appear enough in the text. For example &quot;Peter :&quot; is only
+ recognized if it appears in two consecutive lines.
+ 
+ <P> 
+ Additionaly, Alpine recognizes indent-strings and justifies text in a
+ paragraph to the right of indent-string, padding with spaces if necessary.
+ An indent string is one which you use to delimit elements of a list. For
+ example, if you were to write a list of groceries, one may write:
+ 
+ <UL>
+ <LI> Fruit
+ <LI> Bread
+ <LI> Eggs
+ </UL>
+ 
+ <P> 
+ In this case the character &quot;*&quot; is the indent-string. Aline
+ recognizes numbers (0, 1, 2.5, etc) also as indent-strings, and certain
+ combinations of spaces, periods, and parenthesis. In any case, numbers are
+ recognized <B>ONLY</B> if the line preceeding the given line is empty or
+ ends in one of the characters &quot;.&quot; or &quot;:&quot;.
+ In addition to the explanation of what constitutes a paragraph above, a
+ new paragraph is recognized when an indent-string is found in it (and
+ validated according to the above stated rules).
+ 
+ <P>
  &lt;End of help on this topic&gt;
  </BODY>
  </HTML>
diff -rc alpine-2.26/pith/reply.c alpine-2.26.fillpara/pith/reply.c
*** alpine-2.26/pith/reply.c	2022-06-02 18:14:00.495274738 -0600
--- alpine-2.26.fillpara/pith/reply.c	2022-06-02 18:14:57.059136043 -0600
***************
*** 2834,2839 ****
--- 2834,2842 ----
   		if(flow_res && ps_global->reply.use_flowed)
  		  wrapflags |= GFW_FLOW_RESULT;
  
+ 		filters[filtcnt].filter = gf_quote_test;
+ 		filters[filtcnt++].data = gf_line_test_opt(select_quote, NULL);
+ 
  		filters[filtcnt].filter = gf_wrap;
  		/* 
  		 * The 80 will cause longer lines than what is likely
***************
*** 2927,2933 ****
  	dq.do_color   = 0;
  	dq.delete_all = 1;
  
! 	filters[filtcnt].filter = gf_line_test;
  	filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
      }
  
--- 2930,2936 ----
  	dq.do_color   = 0;
  	dq.delete_all = 1;
  
! 	filters[filtcnt].filter = gf_quote_test;
  	filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
      }
  
diff -rc alpine-2.26/pith/state.c alpine-2.26.fillpara/pith/state.c
*** alpine-2.26/pith/state.c	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pith/state.c	2022-06-02 18:14:57.059136043 -0600
***************
*** 231,236 ****
--- 231,238 ----
      if((*pps)->kw_colors)
        free_spec_colors(&(*pps)->kw_colors);
  
+     free_allowed_qstr();
+ 
      if((*pps)->atmts){
  	int i;
  
diff -rc alpine-2.26/pith/state.h alpine-2.26.fillpara/pith/state.h
*** alpine-2.26/pith/state.h	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pith/state.h	2022-06-02 18:14:57.087135974 -0600
***************
*** 264,269 ****
--- 264,271 ----
      SPEC_COLOR_S *hdr_colors;		/* list of configured colors for view */
      SPEC_COLOR_S *index_token_colors;	/* list of configured colors for index */
  
+     char	*prefix;		/* prefix for fillpara */
+     char	**list_qstr;		/* list of known quote strings */
      short	 init_context;
  
      struct { 
diff -rc alpine-2.26/pith/text.c alpine-2.26.fillpara/pith/text.c
*** alpine-2.26/pith/text.c	2022-06-02 18:14:00.491274749 -0600
--- alpine-2.26.fillpara/pith/text.c	2022-06-02 18:14:57.091135965 -0600
***************
*** 88,94 ****
      char       *err, *charset;
      int		filtcnt = 0, error_found = 0, column, wrapit;
      int         is_in_sig = OUT_SIG_BLOCK;
!     int         is_flowed_msg = 0;
      int         is_delsp_yes = 0;
      int         filt_only_c0 = 0;
      char       *parmval;
--- 88,94 ----
      char       *err, *charset;
      int		filtcnt = 0, error_found = 0, column, wrapit;
      int         is_in_sig = OUT_SIG_BLOCK;
!     int         is_flowed_msg = 0, add_me = 1, doraw = RAWSTRING;
      int         is_delsp_yes = 0;
      int         filt_only_c0 = 0;
      char       *parmval;
***************
*** 176,182 ****
  	   && pico_usingcolor()
  	   && VAR_SIGNATURE_FORE_COLOR
  	   && VAR_SIGNATURE_BACK_COLOR){
! 	    filters[filtcnt].filter = gf_line_test;
  	    filters[filtcnt++].data = gf_line_test_opt(color_signature,
  						       &is_in_sig);
  	}
--- 176,182 ----
  	   && pico_usingcolor()
  	   && VAR_SIGNATURE_FORE_COLOR
  	   && VAR_SIGNATURE_BACK_COLOR){
! 	    filters[filtcnt].filter = gf_quote_test;
  	    filters[filtcnt++].data = gf_line_test_opt(color_signature,
  						       &is_in_sig);
  	}
***************
*** 194,202 ****
  	   && pico_usingcolor()
  	   && VAR_QUOTE1_FORE_COLOR
  	   && VAR_QUOTE1_BACK_COLOR){
! 	    filters[filtcnt].filter = gf_line_test;
! 	    filters[filtcnt++].data = gf_line_test_opt(color_a_quote,
! 						       &is_flowed_msg);
  	}
      }
      else if(!strucmp(att->body->subtype, "richtext")){
--- 194,202 ----
  	   && pico_usingcolor()
  	   && VAR_QUOTE1_FORE_COLOR
  	   && VAR_QUOTE1_BACK_COLOR){
! 	    add_me = 0;
! 	    filters[filtcnt].filter = gf_quote_test;
! 	    filters[filtcnt++].data = gf_line_test_opt(color_a_quote, &is_flowed_msg);
  	}
      }
      else if(!strucmp(att->body->subtype, "richtext")){
***************
*** 277,282 ****
--- 277,287 ----
  	}
      }
  
+     if (add_me){
+       filters[filtcnt].filter = gf_quote_test;
+       filters[filtcnt++].data = gf_line_test_opt(select_quote, &doraw);
+     }
+ 
      /*
       * If the message is not flowed, we do the quote suppression before
       * the wrapping, because the wrapping does not preserve the quote
***************
*** 301,307 ****
  	    dq.handlesp   = handlesp;
  	    dq.do_color   = (!(flags & FM_NOCOLOR) && pico_usingcolor());
  
! 	    filters[filtcnt].filter = gf_line_test;
  	    filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
  	}
  	if(ps_global->VAR_QUOTE_REPLACE_STRING
--- 306,312 ----
  	    dq.handlesp   = handlesp;
  	    dq.do_color   = (!(flags & FM_NOCOLOR) && pico_usingcolor());
  
! 	    filters[filtcnt].filter = gf_quote_test;
  	    filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
  	}
  	if(ps_global->VAR_QUOTE_REPLACE_STRING
***************
*** 360,366 ****
  	dq.handlesp   = handlesp;
  	dq.do_color   = (!(flags & FM_NOCOLOR) && pico_usingcolor());
  
! 	filters[filtcnt].filter = gf_line_test;
  	filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
      }
  
--- 365,371 ----
  	dq.handlesp   = handlesp;
  	dq.do_color   = (!(flags & FM_NOCOLOR) && pico_usingcolor());
  
! 	filters[filtcnt].filter = gf_quote_test;
  	filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
      }
  
***************
*** 565,571 ****
  {
      DELQ_S *dq;
      char   *lp;
!     int     i, lines, not_a_quote = 0;
      size_t  len = 0;
  
      dq = (DELQ_S *) local;
--- 570,576 ----
  {
      DELQ_S *dq;
      char   *lp;
!     int     i, lines, not_a_quote = 0, code;
      size_t  len = 0;
  
      dq = (DELQ_S *) local;
***************
*** 585,590 ****
--- 590,597 ----
  	for(i = dq->indent_length; i > 0 && !not_a_quote && *lp; i--)
  	  if(*lp++ != SPACE)
  	    not_a_quote++;
+ 	while(isspace((unsigned char) *lp))
+ 	   lp++;
  	
  	/* skip over leading tags */
  	while(!not_a_quote
***************
*** 624,636 ****
  	    }
  	}
  
! 	/* skip over whitespace */
! 	if(!dq->is_flowed)
! 	  while(isspace((unsigned char) *lp))
! 	    lp++;
! 
! 	/* check first character to see if it is a quote */
! 	if(!not_a_quote && *lp != '>')
  	  not_a_quote++;
  
  	if(not_a_quote){
--- 631,642 ----
  	    }
  	}
  
! 	len = lp - line;
! 	if(strlen(tmp_20k_buf) > len)
! 	  strcpy(tmp_20k_buf, tmp_20k_buf+len);
!         code =  (dq->is_flowed ? IS_FLOWED : NO_FLOWED) | DELETEQUO;
! 	select_quote(linenum, lp, ins,  &code);
! 	if (!not_a_quote && !tmp_20k_buf[0])
  	  not_a_quote++;
  
  	if(not_a_quote){