root/usr.bin/mg/yank.c
/*      $OpenBSD: yank.c,v 1.16 2023/03/08 04:43:11 guenther Exp $      */

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

/*
 *      kill ring functions
 */

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

#include "def.h"

#define KBLOCK   8192           /* Kill grow.                    */

static char     *kbufp = NULL;  /* Kill buffer data.             */
static RSIZE     kused = 0;     /* # of bytes used in KB.        */
static RSIZE     ksize = 0;     /* # of bytes allocated in KB.   */
static RSIZE     kstart = 0;    /* # of first used byte in KB.   */

static int       kgrow(int);

/*
 * Delete all of the text saved in the kill buffer.  Called by commands when
 * a new kill context is created. The kill buffer array is released, just in
 * case the buffer has grown to an immense size.  No errors.
 */
void
kdelete(void)
{
        if (kbufp != NULL) {
                free(kbufp);
                kbufp = NULL;
                kstart = kused = ksize = 0;
        }
}

/*
 * Insert a character to the kill buffer, enlarging the buffer if there
 * isn't any room. Always grow the buffer in chunks, on the assumption
 * that if you put something in the kill buffer you are going to put more
 * stuff there too later. Return TRUE if all is well, and FALSE on errors.
 * Print a message on errors.  Dir says whether to put it at back or front.
 * This call is ignored if  KNONE is set.
 */
int
kinsert(int c, int dir)
{
        if (dir == KNONE)
                return (TRUE);
        if (kused == ksize && dir == KFORW && kgrow(dir) == FALSE)
                return (FALSE);
        if (kstart == 0 && dir == KBACK && kgrow(dir) == FALSE)
                return (FALSE);
        if (dir == KFORW)
                kbufp[kused++] = c;
        else if (dir == KBACK)
                kbufp[--kstart] = c;
        else
                panic("broken kinsert call");   /* Oh shit! */
        return (TRUE);
}

/*
 * kgrow - just get more kill buffer for the callee. If dir = KBACK
 * we are trying to get space at the beginning of the kill buffer.
 */
static int
kgrow(int dir)
{
        int      nstart;
        char    *nbufp;

        if ((unsigned)(ksize + KBLOCK) <= (unsigned)ksize) {
                /* probably 16 bit unsigned */
                dobeep();
                ewprintf("Kill buffer size at maximum");
                return (FALSE);
        }
        if ((nbufp = malloc((unsigned)(ksize + KBLOCK))) == NULL) {
                dobeep();
                ewprintf("Can't get %ld bytes", (long)(ksize + KBLOCK));
                return (FALSE);
        }
        nstart = (dir == KBACK) ? (kstart + KBLOCK) : (KBLOCK / 4);
        bcopy(&(kbufp[kstart]), &(nbufp[nstart]), (int)(kused - kstart));
        free(kbufp);
        kbufp = nbufp;
        ksize += KBLOCK;
        kused = kused - kstart + nstart;
        kstart = nstart;
        return (TRUE);
}

/*
 * This function gets characters from the kill buffer. If the character
 * index "n" is off the end, it returns "-1". This lets the caller just
 * scan along until it gets a "-1" back.
 */
int
kremove(int n)
{
        if (n < 0 || n + kstart >= kused)
                return (-1);
        return (CHARMASK(kbufp[n + kstart]));
}

/*
 * Copy a string into the kill buffer. kflag gives direction.
 * if KNONE, do nothing.
 */
int
kchunk(char *cp1, RSIZE chunk, int kflag)
{
        /*
         * HACK - doesn't matter, and fixes back-over-nl bug for empty
         *      kill buffers.
         */
        if (kused == kstart)
                kflag = KFORW;

        if (kflag & KFORW) {
                while (ksize - kused < chunk)
                        if (kgrow(kflag) == FALSE)
                                return (FALSE);
                bcopy(cp1, &(kbufp[kused]), (int)chunk);
                kused += chunk;
        } else if (kflag & KBACK) {
                while (kstart < chunk)
                        if (kgrow(kflag) == FALSE)
                                return (FALSE);
                bcopy(cp1, &(kbufp[kstart - chunk]), (int)chunk);
                kstart -= chunk;
        }

        return (TRUE);
}

/*
 * Kill line.  If called without an argument, it kills from dot to the end
 * of the line, unless it is at the end of the line, when it kills the
 * newline.  If called with an argument of 0, it kills from the start of the
 * line to dot.  If called with a positive argument, it kills from dot
 * forward over that number of newlines.  If called with a negative argument
 * it kills any text before dot on the current line, then it kills back
 * abs(arg) lines.
 */
int
killline(int f, int n)
{
        struct line     *nextp;
        RSIZE    chunk;
        int      i, c;

        /* clear kill buffer if last wasn't a kill */
        if ((lastflag & CFKILL) == 0)
                kdelete();
        thisflag |= CFKILL;
        if (!(f & FFARG)) {
                for (i = curwp->w_doto; i < llength(curwp->w_dotp); ++i)
                        if ((c = lgetc(curwp->w_dotp, i)) != ' ' && c != '\t')
                                break;
                if (i == llength(curwp->w_dotp))
                        chunk = llength(curwp->w_dotp) - curwp->w_doto + 1;
                else {
                        chunk = llength(curwp->w_dotp) - curwp->w_doto;
                        if (chunk == 0)
                                chunk = 1;
                }
        } else if (n > 0) {
                chunk = llength(curwp->w_dotp) - curwp->w_doto;
                nextp = lforw(curwp->w_dotp);
                if (nextp != curbp->b_headp)
                        chunk++;                /* newline */
                if (nextp == curbp->b_headp)
                        goto done;              /* EOL */
                i = n;
                while (--i) {
                        chunk += llength(nextp);
                        nextp = lforw(nextp);
                        if (nextp != curbp->b_headp)
                                chunk++;        /* newline */
                        if (nextp == curbp->b_headp)
                                break;          /* EOL */
                }
        } else {
                /* n <= 0 */
                chunk = curwp->w_doto;
                curwp->w_doto = 0;
                i = n;
                while (i++) {
                        if (lforw(curwp->w_dotp))
                                chunk++;
                        curwp->w_dotp = lback(curwp->w_dotp);
                        curwp->w_rflag |= WFMOVE;
                        chunk += llength(curwp->w_dotp);
                }
        }
        /*
         * KFORW here is a bug.  Should be KBACK/KFORW, but we need to
         * rewrite the ldelete code (later)?
         */
done:
        if (chunk)
                return (ldelete(chunk, KFORW));
        return (TRUE);
}

/*
 * Yank text back from the kill buffer.  This is really easy.  All of the work
 * is done by the standard insert routines.  All you do is run the loop, and
 * check for errors.  The blank lines are inserted with a call to "newline"
 * instead of a call to "lnewline" so that the magic stuff that happens when
 * you type a carriage return also happens when a carriage return is yanked
 * back from the kill buffer.  An attempt has been made to fix the cosmetic
 * bug associated with a yank when dot is on the top line of the window
 * (nothing moves, because all of the new text landed off screen).
 */
int
yank(int f, int n)
{
        struct line     *lp;
        int      c, i, nline;

        if (n < 0)
                return (FALSE);

        /* newline counting */
        nline = 0;

        undo_boundary_enable(FFRAND, 0);
        while (n--) {
                /* mark around last yank */
                isetmark();
                i = 0;
                while ((c = kremove(i)) >= 0) {
                        if (c == *curbp->b_nlchr) {
                                if (enewline(FFRAND, 1) == FALSE)
                                        return (FALSE);
                                ++nline;
                        } else {
                                if (linsert(1, c) == FALSE)
                                        return (FALSE);
                        }
                        ++i;
                }
        }
        /* cosmetic adjustment */
        lp = curwp->w_linep;

        /* if offscreen insert */
        if (curwp->w_dotp == lp) {
                while (nline-- && lback(lp) != curbp->b_headp)
                        lp = lback(lp);
                /* adjust framing */
                curwp->w_linep = lp;
                curwp->w_rflag |= WFFULL;
        }
        undo_boundary_enable(FFRAND, 1);
        return (TRUE);
}