root/usr.bin/mg/word.c
/*      $OpenBSD: word.c,v 1.23 2026/02/23 09:36:50 op Exp $    */

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

/*
 *              Word mode commands.
 * The routines in this file implement commands that work word at a time.
 * There are all sorts of word mode commands.
 */

#include <sys/queue.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "def.h"

RSIZE   countfword(void);
int     grabword(char **);

/*
 * Move the cursor backward by "n" words. All of the details of motion are
 * performed by the "backchar" and "forwchar" routines.
 */
int
backword(int f, int n)
{
        if (n < 0)
                return (forwword(f | FFRAND, -n));
        if (backchar(FFRAND, 1) == FALSE)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (backchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                }
                while (inword() != FALSE) {
                        if (backchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                }
        }
        return (forwchar(FFRAND, 1));
}

/*
 * Move the cursor forward by the specified number of words.  All of the
 * motion is done by "forwchar".
 */
int
forwword(int f, int n)
{
        if (n < 0)
                return (backword(f | FFRAND, -n));
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                }
                while (inword() != FALSE) {
                        if (forwchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                }
        }
        return (TRUE);
}

/*
 * Transpose 2 words. 
 * The function below is artificially restricted to only a maximum of 1 iteration
 * at the moment because the 'undo' functionality within mg needs amended for
 * multiple movements of point, backwards and forwards.
 */
int
transposeword(int f, int n)
{
        struct line     *tmp1_w_dotp = NULL;
        struct line     *tmp2_w_dotp = NULL;
        int              tmp2_w_doto = 0;
        int              tmp1_w_dotline = 0;
        int              tmp2_w_dotline = 0;
        int              tmp1_w_doto;
        int              i;             /* start-of-line space counter */
        int              ret, s;
        int              newline;
        int              leave = 0;
        int              tmp_len;
        char            *word1 = NULL;
        char            *word2 = NULL;
        char            *chr;

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

        n = 1; /* remove this line to allow muliple-iterations */

        if ((s = checkdirty(curbp)) != TRUE)
                return (s);
        if (curbp->b_flag & BFREADONLY) {
                dobeep();
                ewprintf("Buffer is read-only");
                return (FALSE);
        }
        undo_boundary_enable(FFRAND, 0);

        /* go backwards to find the start of a word to transpose. */
        (void)backword(FFRAND, 1);
        ret = grabword(&word1);
        if (ret == ABORT) {
                ewprintf("No word to the left to tranpose.");
                return (FALSE);
        }
        if (ret < 0) {
                dobeep();
                ewprintf("Error copying word: %s", strerror(ret));
                free(word1);
                return (FALSE);
        }

        while (n-- > 0) {
                i = 0;
                newline = 0;

                tmp1_w_doto = curwp->w_doto;
                tmp1_w_dotline = curwp->w_dotline;
                tmp1_w_dotp = curwp->w_dotp;

                /* go forward and find next word. */
                while (inword() == FALSE) {
                        if (forwchar(FFRAND, 1) == FALSE) {
                                leave = 1;
                                if (tmp1_w_dotline < curwp->w_dotline)
                                        curwp->w_dotline--;
                                ewprintf("Don't have two things to transpose");
                                break;
                        }
                        if (curwp->w_doto == 0) {
                                newline = 1;
                                i = 0;
                        } else if (newline)
                                i++;
                }
                if (leave) {
                        tmp2_w_doto = tmp1_w_doto;
                        tmp2_w_dotline = tmp1_w_dotline;
                        tmp2_w_dotp = tmp1_w_dotp;
                        break;
                }
                tmp2_w_doto = curwp->w_doto;
                tmp2_w_dotline = curwp->w_dotline;
                tmp2_w_dotp = curwp->w_dotp;

                ret = grabword(&word2);
                if (ret < 0 || ret == ABORT) {
                        dobeep();
                        ewprintf("Error copying word: %s", strerror(ret));
                        free(word1);
                        return (FALSE);
                }
                tmp_len = strlen(word2);
                tmp2_w_doto += tmp_len;

                curwp->w_doto = tmp1_w_doto;
                curwp->w_dotline = tmp1_w_dotline;
                curwp->w_dotp = tmp1_w_dotp;

                /* insert shuffled along word */
                for (chr = word2; *chr != '\0'; ++chr)
                        linsert(1, *chr);

                if (newline)
                        tmp2_w_doto = i;

                curwp->w_doto = tmp2_w_doto;
                curwp->w_dotline = tmp2_w_dotline;
                curwp->w_dotp = tmp2_w_dotp;

                free(word2);
                word2 = NULL;
        }
        curwp->w_doto = tmp2_w_doto;
        curwp->w_dotline = tmp2_w_dotline;
        curwp->w_dotp = tmp2_w_dotp;

        /* insert very first word in its new position */
        for (chr = word1; *chr != '\0'; ++chr)
                linsert(1, *chr);

        if (leave)
                (void)backword(FFRAND, 1);

        free(word1);
        free(word2);

        undo_boundary_enable(FFRAND, 1);

        return (TRUE);
}

/*
 * copy and delete word.
*/
int
grabword(char **word)
{
        size_t len = 0, cap = 0;
        char *t;

        while (inword() == TRUE) {
                if (cap == 0 || len == cap - 1) {
                        t = recallocarray(*word, cap, cap + 8, 1);
                        if (t == NULL) {
                                free(*word);
                                *word = NULL;
                                return (errno);
                        }
                        cap += 8;
                        *word = t;
                }

                (*word)[len++] = lgetc(curwp->w_dotp, curwp->w_doto);
                (void)forwdel(FFRAND, 1);
        }
        if (*word == NULL)
                return (ABORT);
        return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words.  As you move,
 * convert any characters to upper case.
 */
int
upperword(int f, int n)
{
        int     c, s;
        RSIZE   size;

        if ((s = checkdirty(curbp)) != TRUE)
                return (s);
        if (curbp->b_flag & BFREADONLY) {
                dobeep();
                ewprintf("Buffer is read-only");
                return (FALSE);
        }

        if (n < 0)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                }
                size = countfword();
                undo_add_change(curwp->w_dotp, curwp->w_doto, size);

                while (inword() != FALSE) {
                        c = lgetc(curwp->w_dotp, curwp->w_doto);
                        if (ISLOWER(c) != FALSE) {
                                c = TOUPPER(c);
                                lputc(curwp->w_dotp, curwp->w_doto, c);
                                lchange(WFFULL);
                        }
                        if (forwchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                }
        }
        return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words.  As you move
 * convert characters to lower case.
 */
int
lowerword(int f, int n)
{
        int     c, s;
        RSIZE   size;

        if ((s = checkdirty(curbp)) != TRUE)
                return (s);
        if (curbp->b_flag & BFREADONLY) {
                dobeep();
                ewprintf("Buffer is read-only");
                return (FALSE);
        }
        if (n < 0)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                }
                size = countfword();
                undo_add_change(curwp->w_dotp, curwp->w_doto, size);

                while (inword() != FALSE) {
                        c = lgetc(curwp->w_dotp, curwp->w_doto);
                        if (ISUPPER(c) != FALSE) {
                                c = TOLOWER(c);
                                lputc(curwp->w_dotp, curwp->w_doto, c);
                                lchange(WFFULL);
                        }
                        if (forwchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                }
        }
        return (TRUE);
}

/*
 * Move the cursor forward by the specified number of words.  As you move
 * convert the first character of the word to upper case, and subsequent
 * characters to lower case.  Error if you try to move past the end of the
 * buffer.
 */
int
capword(int f, int n)
{
        int     c, s;
        RSIZE   size;

        if ((s = checkdirty(curbp)) != TRUE)
                return (s);
        if (curbp->b_flag & BFREADONLY) {
                dobeep();
                ewprintf("Buffer is read-only");
                return (FALSE);
        }

        if (n < 0)
                return (FALSE);
        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                }
                size = countfword();
                undo_add_change(curwp->w_dotp, curwp->w_doto, size);

                if (inword() != FALSE) {
                        c = lgetc(curwp->w_dotp, curwp->w_doto);
                        if (ISLOWER(c) != FALSE) {
                                c = TOUPPER(c);
                                lputc(curwp->w_dotp, curwp->w_doto, c);
                                lchange(WFFULL);
                        }
                        if (forwchar(FFRAND, 1) == FALSE)
                                return (TRUE);
                        while (inword() != FALSE) {
                                c = lgetc(curwp->w_dotp, curwp->w_doto);
                                if (ISUPPER(c) != FALSE) {
                                        c = TOLOWER(c);
                                        lputc(curwp->w_dotp, curwp->w_doto, c);
                                        lchange(WFFULL);
                                }
                                if (forwchar(FFRAND, 1) == FALSE)
                                        return (TRUE);
                        }
                }
        }
        return (TRUE);
}

/*
 * Count characters in word, from current position
 */
RSIZE
countfword()
{
        RSIZE            size;
        struct line     *dotp;
        int              doto;

        dotp = curwp->w_dotp;
        doto = curwp->w_doto;
        size = 0;

        while (inword() != FALSE) {
                if (forwchar(FFRAND, 1) == FALSE)
                        /* hit the end of the buffer */
                        goto out;
                ++size;
        }
out:
        curwp->w_dotp = dotp;
        curwp->w_doto = doto;
        return (size);
}


/*
 * Kill forward by "n" words.
 */
int
delfword(int f, int n)
{
        RSIZE            size;
        struct line     *dotp;
        int              doto;
        int s;

        if ((s = checkdirty(curbp)) != TRUE)
                return (s);
        if (curbp->b_flag & BFREADONLY) {
                dobeep();
                ewprintf("Buffer is read-only");
                return (FALSE);
        }
        if (n < 0)
                return (FALSE);

        /* purge kill buffer */
        if ((lastflag & CFKILL) == 0)
                kdelete();

        thisflag |= CFKILL;
        dotp = curwp->w_dotp;
        doto = curwp->w_doto;
        size = 0;

        while (n--) {
                while (inword() == FALSE) {
                        if (forwchar(FFRAND, 1) == FALSE)
                                /* hit the end of the buffer */
                                goto out;
                        ++size;
                }
                while (inword() != FALSE) {
                        if (forwchar(FFRAND, 1) == FALSE)
                                /* hit the end of the buffer */
                                goto out;
                        ++size;
                }
        }
out:
        curwp->w_dotp = dotp;
        curwp->w_doto = doto;
        return (ldelete(size, KFORW));
}

/*
 * Kill backwards by "n" words.  The rules for success and failure are now
 * different, to prevent strange behavior at the start of the buffer.  The
 * command only fails if something goes wrong with the actual delete of the
 * characters.  It is successful even if no characters are deleted, or if you
 * say delete 5 words, and there are only 4 words left.  I considered making
 * the first call to "backchar" special, but decided that that would just be
 * weird. Normally this is bound to "M-Rubout" and to "M-Backspace".
 */
int
delbword(int f, int n)
{
        RSIZE   size;
        int s;

        if ((s = checkdirty(curbp)) != TRUE)
                return (s);
        if (curbp->b_flag & BFREADONLY) {
                dobeep();
                ewprintf("Buffer is read-only");
                return (FALSE);
        }

        if (n < 0)
                return (FALSE);

        /* purge kill buffer */
        if ((lastflag & CFKILL) == 0)
                kdelete();
        thisflag |= CFKILL;
        if (backchar(FFRAND, 1) == FALSE)
                /* hit buffer start */
                return (TRUE);

        /* one deleted */
        size = 1;
        while (n--) {
                while (inword() == FALSE) {
                        if (backchar(FFRAND, 1) == FALSE)
                                /* hit buffer start */
                                goto out;
                        ++size;
                }
                while (inword() != FALSE) {
                        if (backchar(FFRAND, 1) == FALSE)
                                /* hit buffer start */
                                goto out;
                        ++size;
                }
        }
        if (forwchar(FFRAND, 1) == FALSE)
                return (FALSE);

        /* undo assumed delete */
        --size;
out:
        return (ldelete(size, KBACK));
}

/*
 * Return TRUE if the character at dot is a character that is considered to be
 * part of a word. The word character list is hard coded. Should be settable.
 */
int
inword(void)
{
        /* can't use lgetc in ISWORD due to bug in OSK cpp */
        return (curwp->w_doto != llength(curwp->w_dotp) &&
            ISWORD(curwp->w_dotp->l_text[curwp->w_doto]));
}