root/usr.bin/mg/paragraph.c
/*      $OpenBSD: paragraph.c,v 1.49 2023/04/21 13:39:37 op Exp $       */

/* This file is in the public domain. */

/*
 * Code for dealing with paragraphs and filling. Adapted from MicroEMACS 3.6
 * and GNU-ified by mwm@ucbvax.  Several bug fixes by blarson@usc-oberon.
 */

#include <sys/queue.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#include "def.h"

static int      fillcol = 70;

#define MAXWORD 256

static int      findpara(void);
static int      do_gotoeop(int, int, int *);

/*
 * Move to start of paragraph.
 * Move backwards by line, checking from the 1st character forwards for the
 * existence a non-space. If a non-space character is found, move to the 
 * preceding line. Keep doing this until a line with only spaces is found or
 * the start of buffer.
 */
int
gotobop(int f, int n)
{
        int col, nospace;

        /* the other way... */
        if (n < 0)
                return (gotoeop(f, -n));

        while (n-- > 0) {
                nospace = 0;
                while (lback(curwp->w_dotp) != curbp->b_headp) {
                        curwp->w_doto = 0;
                        col = 0;

                        while (col < llength(curwp->w_dotp) &&
                            (isspace(lgetc(curwp->w_dotp, col))))
                                col++;

                        if (col >= llength(curwp->w_dotp)) {
                                if (nospace)
                                        break;
                        } else
                                nospace = 1;

                        curwp->w_dotline--;
                        curwp->w_dotp = lback(curwp->w_dotp);
                }
        }
        /* force screen update */
        curwp->w_rflag |= WFMOVE;
        return (TRUE);
}

/*
 * Move to end of paragraph.
 * See comments for gotobop(). Same, but moving forwards.
 */
int
gotoeop(int f, int n)
{
        int i;

        return(do_gotoeop(f, n, &i));
}

int
do_gotoeop(int f, int n, int *i)
{
        int col, nospace, j = 0;

        /* the other way... */
        if (n < 0)
                return (gotobop(f, -n));

        /* for each one asked for */
        while (n-- > 0) {
                *i = ++j;
                nospace = 0;
                while (lforw(curwp->w_dotp) != curbp->b_headp) {
                        col = 0;
                        curwp->w_doto = 0;

                        while (col < llength(curwp->w_dotp) &&
                            (isspace(lgetc(curwp->w_dotp, col))))
                                col++;

                        if (col >= llength(curwp->w_dotp)) {
                                if (nospace)
                                        break;
                        } else
                                nospace = 1;

                        curwp->w_dotp = lforw(curwp->w_dotp);
                        curwp->w_dotline++;

                }
        }
        /* do not continue after end of buffer */
        if (lforw(curwp->w_dotp) == curbp->b_headp) {
                gotoeol(FFRAND, 1);
                curwp->w_rflag |= WFMOVE;
                return (FALSE);
        }

        /* force screen update */
        curwp->w_rflag |= WFMOVE;
        return (TRUE);
}

/*
 * Justify a paragraph.  Fill the current paragraph according to the current
 * fill column.
 */
int
fillpara(int f, int n)
{
        int      c;             /* current char during scan             */
        int      wordlen;       /* length of current word               */
        int      clength;       /* position on line during fill         */
        int      i;             /* index during word copy               */
        int      eopflag;       /* Are we at the End-Of-Paragraph?      */
        int      firstflag;     /* first word? (needs no space)         */
        int      newlength;     /* tentative new line length            */
        int      eolflag;       /* was at end of line                   */
        int      retval;        /* return value                         */
        struct line     *eopline;       /* pointer to line just past EOP        */
        char     wbuf[MAXWORD]; /* buffer for current word              */

        if (n == 0)
                return (TRUE);

        undo_boundary_enable(FFRAND, 0);

        /* record the pointer to the line just past the EOP */
        (void)gotoeop(FFRAND, 1);
        if (curwp->w_doto != 0) {
                /* paragraph ends at end of buffer */
                (void)lnewline();
                eopline = lforw(curwp->w_dotp);
        } else
                eopline = curwp->w_dotp;

        /* and back top the beginning of the paragraph */
        (void)gotobop(FFRAND, 1);

        /* initialize various info */
        while (inword() == 0 && forwchar(FFRAND, 1));

        clength = curwp->w_doto;
        wordlen = 0;

        /* scan through lines, filling words */
        firstflag = TRUE;
        eopflag = FALSE;
        while (!eopflag) {

                /* get the next character in the paragraph */
                if ((eolflag = (curwp->w_doto == llength(curwp->w_dotp)))) {
                        c = ' ';
                        if (lforw(curwp->w_dotp) == eopline)
                                eopflag = TRUE;
                } else
                        c = lgetc(curwp->w_dotp, curwp->w_doto);

                /* and then delete it */
                if (ldelete((RSIZE) 1, KNONE) == FALSE && !eopflag) {
                        retval = FALSE;
                        goto cleanup;
                }

                /* if not a separator, just add it in */
                if (c != ' ' && c != '\t') {
                        if (wordlen < MAXWORD - 1)
                                wbuf[wordlen++] = c;
                        else {
                                /*
                                 * You lose chars beyond MAXWORD if the word
                                 * is too long. I'm too lazy to fix it now; it
                                 * just silently truncated the word before,
                                 * so I get to feel smug.
                                 */
                                ewprintf("Word too long!");
                        }
                } else if (wordlen) {

                        /* calculate tentative new length with word added */
                        newlength = clength + 1 + wordlen;

                        /*
                         * if at end of line or at doublespace and previous
                         * character was one of '.','?','!' doublespace here.
                         * behave the same way if a ')' is preceded by a
                         * [.?!] and followed by a doublespace.
                         */
                        if (dblspace && (!eopflag && ((eolflag ||
                            curwp->w_doto == llength(curwp->w_dotp) ||
                            (c = lgetc(curwp->w_dotp, curwp->w_doto)) == ' '
                            || c == '\t') && (ISEOSP(wbuf[wordlen - 1]) ||
                            (wbuf[wordlen - 1] == ')' && wordlen >= 2 &&
                            ISEOSP(wbuf[wordlen - 2])))) &&
                            wordlen < MAXWORD - 1))
                                wbuf[wordlen++] = ' ';

                        /* at a word break with a word waiting */
                        if (newlength <= fillcol) {
                                /* add word to current line */
                                if (!firstflag) {
                                        (void)linsert(1, ' ');
                                        ++clength;
                                }
                                firstflag = FALSE;
                        } else {
                                if (curwp->w_doto > 0 &&
                                    lgetc(curwp->w_dotp, curwp->w_doto - 1) == ' ') {
                                        curwp->w_doto -= 1;
                                        (void)ldelete((RSIZE) 1, KNONE);
                                }
                                /* start a new line */
                                (void)lnewline();
                                clength = 0;
                        }

                        /* and add the word in in either case */
                        for (i = 0; i < wordlen; i++) {
                                (void)linsert(1, wbuf[i]);
                                ++clength;
                        }
                        wordlen = 0;
                }
        }
        /* and add a last newline for the end of our new paragraph */
        (void)lnewline();

        /*
         * We really should wind up where we started, (which is hard to keep
         * track of) but I think the end of the last line is better than the
         * beginning of the blank line.
         */
        (void)backchar(FFRAND, 1);
        retval = TRUE;
cleanup:
        undo_boundary_enable(FFRAND, 1);
        return (retval);
}

/*
 * Delete n paragraphs. Move to the beginning of the current paragraph, or if
 * the cursor is on an empty line, move down the buffer to the first line with
 * non-space characters. Then mark n paragraphs and delete.
 */
int
killpara(int f, int n)
{
        int     lineno, status;

        if (n == 0)
                return (TRUE);

        if (findpara() == FALSE)
                return (TRUE);

        /* go to the beginning of the paragraph */
        (void)gotobop(FFRAND, 1);

        /* take a note of the line number for after deletions and set mark */
        lineno = curwp->w_dotline;
        curwp->w_markp = curwp->w_dotp;
        curwp->w_marko = curwp->w_doto;

        (void)gotoeop(FFRAND, n);

        if ((status = killregion(FFRAND, 1)) != TRUE)
                return (status);

        curwp->w_dotline = lineno;
        return (TRUE);
}

/*
 * Mark n paragraphs starting with the n'th and working our way backwards.
 * This leaves the cursor at the beginning of the paragraph where markpara()
 * was invoked.
 */
int
markpara(int f, int n)
{
        int i = 0;

        if (n == 0)
                return (TRUE);

        clearmark(FFARG, 0);

        if (findpara() == FALSE)
                return (TRUE);

        (void)do_gotoeop(FFRAND, n, &i);

        /* set the mark here */
        curwp->w_markp = curwp->w_dotp;
        curwp->w_marko = curwp->w_doto;

        (void)gotobop(FFRAND, i);

        return (TRUE);
}

/*
 * Transpose the current paragraph with the following paragraph. If invoked
 * multiple times, transpose to the n'th paragraph. If invoked between 
 * paragraphs, move to the previous paragraph, then continue.
 */
int
transposepara(int f, int n)
{
        int     i = 0, status;
        char    flg;

        if (n == 0)
                return (TRUE);

        undo_boundary_enable(FFRAND, 0);

        /* find a paragraph, set mark, then goto the end */
        gotobop(FFRAND, 1);
        curwp->w_markp = curwp->w_dotp;
        curwp->w_marko = curwp->w_doto;
        (void)gotoeop(FFRAND, 1);

        /* take a note of buffer flags - we may need them */
        flg = curbp->b_flag;    

        /* clean out kill buffer then kill region */
        kdelete();
        if ((status = killregion(FFRAND, 1)) != TRUE)
                return (status);

        /* 
         * Now step through n paragraphs. If we reach the end of buffer,
         * stop and paste the killed region back, then display a message.
         */
        if (do_gotoeop(FFRAND, n, &i) == FALSE) {
                ewprintf("Cannot transpose paragraph, end of buffer reached.");
                (void)gotobop(FFRAND, i);
                (void)yank(FFRAND, 1);
                curbp->b_flag = flg;    
                return (FALSE);
        }
        (void)yank(FFRAND, 1);

        undo_boundary_enable(FFRAND, 1);

        return (TRUE);
}

/*
 * Go down the buffer until we find a line with non-space characters.
 */
int
findpara(void)
{
        int     col, nospace = 0;

        /* we move forward to find a para to mark */
        do {
                curwp->w_doto = 0;
                col = 0;

                /* check if we are on a blank line */
                while (col < llength(curwp->w_dotp)) {
                        if (!isspace(lgetc(curwp->w_dotp, col)))
                                nospace = 1;
                        col++;
                }
                if (nospace)
                        break;

                if (lforw(curwp->w_dotp) == curbp->b_headp)
                        return (FALSE);

                curwp->w_dotp = lforw(curwp->w_dotp);   
                curwp->w_dotline++;
        } while (1);

        return (TRUE);
}

/*
 * Insert char with work wrap.  Check to see if we're past fillcol, and if so,
 * justify this line.  As a last step, justify the line.
 */
int
fillword(int f, int n)
{
        char    c;
        int     col, i, nce;

        for (i = col = 0; col <= fillcol; ++i, ++col) {
                if (i == curwp->w_doto)
                        return selfinsert(f, n);
                c = lgetc(curwp->w_dotp, i);
                if (c == '\t')
                        col = ntabstop(col, curwp->w_bufp->b_tabw);
                else if (ISCTRL(c) != FALSE)
                        ++col;
        }
        if (curwp->w_doto != llength(curwp->w_dotp)) {
                (void)selfinsert(f, n);
                nce = llength(curwp->w_dotp) - curwp->w_doto;
        } else
                nce = 0;
        curwp->w_doto = i;

        if ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' && c != '\t')
                do {
                        (void)backchar(FFRAND, 1);
                } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
                    c != '\t' && curwp->w_doto > 0);

        if (curwp->w_doto == 0)
                do {
                        (void)forwchar(FFRAND, 1);
                } while ((c = lgetc(curwp->w_dotp, curwp->w_doto)) != ' ' &&
                    c != '\t' && curwp->w_doto < llength(curwp->w_dotp));

        (void)delwhite(FFRAND, 1);
        (void)lnewline();
        i = llength(curwp->w_dotp) - nce;
        curwp->w_doto = i > 0 ? i : 0;
        curwp->w_rflag |= WFMOVE;
        if (nce == 0 && curwp->w_doto != 0)
                return (fillword(f, n));
        return (TRUE);
}

/*
 * Set fill column to n for justify.
 */
int
setfillcol(int f, int n)
{
        char buf[32], *rep;
        const char *es;
        int nfill;

        if ((f & FFARG) != 0) {
                fillcol = n;
        } else {
                if ((rep = eread("Set fill-column: ", buf, sizeof(buf),
                    EFNEW | EFCR)) == NULL)
                        return (ABORT);
                else if (rep[0] == '\0')
                        return (FALSE);
                nfill = strtonum(rep, 0, INT_MAX, &es);
                if (es != NULL) {
                        dobeep();
                        ewprintf("Invalid fill column: %s", rep);
                        return (FALSE);
                }
                fillcol = nfill;
                ewprintf("Fill column set to %d", fillcol);
        }
        return (TRUE);
}

int
sentencespace(int f, int n)
{
        if (f & FFARG)
                dblspace = n > 1;
        else
                dblspace = !dblspace;

        return (TRUE);
}