/* $OpenBSD: yank.c,v 1.15 2021/03/01 10:51:14 lum 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.
*/
/* ARGSUSED */
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).
*/
/* ARGSUSED */
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);
}