493 lines
12 KiB
C
493 lines
12 KiB
C
/*
|
|
* The functions in this file
|
|
* are a general set of line management
|
|
* utilities. They are the only routines that
|
|
* touch the text. They also touch the buffer
|
|
* and window structures, to make sure that the
|
|
* necessary updating gets done. There are routines
|
|
* in this file that handle the kill buffer too.
|
|
* It isn't here for any good reason.
|
|
*
|
|
* Note that this code only updates the dot and
|
|
* mark values in the window list. Since all the code
|
|
* acts on the current window, the buffer that we
|
|
* are editing must be being displayed, which means
|
|
* that "b_nwnd" is non zero, which means that the
|
|
* dot and mark values in the buffer headers are
|
|
* nonsense.
|
|
*/
|
|
#include <stdio.h>
|
|
#include "ed.h"
|
|
|
|
#define NBLOCK 16 /* Line block chunk size */
|
|
#define KBLOCK 256 /* Kill buffer block size */
|
|
|
|
char *kbufp = NULL; /* Kill buffer data */
|
|
int kused = 0; /* # of bytes used in KB */
|
|
int ksize = 0; /* # of bytes allocated in KB */
|
|
|
|
/*
|
|
* This routine allocates a block
|
|
* of memory large enough to hold a LINE
|
|
* containing "used" characters. The block is
|
|
* always rounded up a bit. Return a pointer
|
|
* to the new block, or NULL if there isn't
|
|
* any memory left. Print a message in the
|
|
* message line if no space.
|
|
*/
|
|
LINE *
|
|
lalloc(used)
|
|
register int used;
|
|
{
|
|
register LINE *lp;
|
|
register int size;
|
|
|
|
size = (used+NBLOCK-1) & ~(NBLOCK-1);
|
|
if (size == 0) /* Assume that an empty */
|
|
size = NBLOCK; /* line is for type-in. */
|
|
if ((lp = (LINE *) malloc(sizeof(LINE)+size)) == NULL) {
|
|
mlwrite("Cannot allocate %d bytes", size);
|
|
return (NULL);
|
|
}
|
|
lp->l_size = size;
|
|
lp->l_used = used;
|
|
return (lp);
|
|
}
|
|
|
|
/*
|
|
* Delete line "lp". Fix all of the
|
|
* links that might point at it (they are
|
|
* moved to offset 0 of the next line.
|
|
* Unlink the line from whatever buffer it
|
|
* might be in. Release the memory. The
|
|
* buffers are updated too; the magic conditions
|
|
* described in the above comments don't hold
|
|
* here.
|
|
*/
|
|
lfree(lp)
|
|
register LINE *lp;
|
|
{
|
|
register BUFFER *bp;
|
|
register WINDOW *wp;
|
|
|
|
wp = wheadp;
|
|
while (wp != NULL) {
|
|
if (wp->w_linep == lp)
|
|
wp->w_linep = lp->l_fp;
|
|
if (wp->w_dotp == lp) {
|
|
wp->w_dotp = lp->l_fp;
|
|
wp->w_doto = 0;
|
|
}
|
|
if (wp->w_markp == lp) {
|
|
wp->w_markp = lp->l_fp;
|
|
wp->w_marko = 0;
|
|
}
|
|
wp = wp->w_wndp;
|
|
}
|
|
bp = bheadp;
|
|
while (bp != NULL) {
|
|
if (bp->b_nwnd == 0) {
|
|
if (bp->b_dotp == lp) {
|
|
bp->b_dotp = lp->l_fp;
|
|
bp->b_doto = 0;
|
|
}
|
|
if (bp->b_markp == lp) {
|
|
bp->b_markp = lp->l_fp;
|
|
bp->b_marko = 0;
|
|
}
|
|
}
|
|
bp = bp->b_bufp;
|
|
}
|
|
lp->l_bp->l_fp = lp->l_fp;
|
|
lp->l_fp->l_bp = lp->l_bp;
|
|
free((char *) lp);
|
|
}
|
|
|
|
/*
|
|
* This routine gets called when
|
|
* a character is changed in place in the
|
|
* current buffer. It updates all of the required
|
|
* flags in the buffer and window system. The flag
|
|
* used is passed as an argument; if the buffer is being
|
|
* displayed in more than 1 window we change EDIT to
|
|
* HARD. Set MODE if the mode line needs to be
|
|
* updated (the "*" has to be set).
|
|
*/
|
|
lchange(flag)
|
|
register int flag;
|
|
{
|
|
register WINDOW *wp;
|
|
|
|
if (curbp->b_nwnd != 1) /* Ensure hard. */
|
|
flag = WFHARD;
|
|
if ((curbp->b_flag&BFCHG) == 0) { /* First change, so */
|
|
flag |= WFMODE; /* update mode lines. */
|
|
curbp->b_flag |= BFCHG;
|
|
}
|
|
wp = wheadp;
|
|
while (wp != NULL) {
|
|
if (wp->w_bufp == curbp)
|
|
wp->w_flag |= flag;
|
|
wp = wp->w_wndp;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Insert "n" copies of the character "c"
|
|
* at the current location of dot. In the easy case
|
|
* all that happens is the text is stored in the line.
|
|
* In the hard case, the line has to be reallocated.
|
|
* When the window list is updated, take special
|
|
* care; I screwed it up once. You always update dot
|
|
* in the current window. You update mark, and a
|
|
* dot in another window, if it is greater than
|
|
* the place where you did the insert. Return TRUE
|
|
* if all is well, and FALSE on errors.
|
|
*/
|
|
linsert(n, c)
|
|
{
|
|
register char *cp1;
|
|
register char *cp2;
|
|
register LINE *lp1;
|
|
register LINE *lp2;
|
|
register LINE *lp3;
|
|
register int doto;
|
|
register int i;
|
|
register WINDOW *wp;
|
|
|
|
lchange(WFEDIT);
|
|
lp1 = curwp->w_dotp; /* Current line */
|
|
if (lp1 == curbp->b_linep) { /* At the end: special */
|
|
if (curwp->w_doto != 0) {
|
|
mlwrite("bug: linsert");
|
|
return (FALSE);
|
|
}
|
|
if ((lp2=lalloc(n)) == NULL) /* Allocate new line */
|
|
return (FALSE);
|
|
lp3 = lp1->l_bp; /* Previous line */
|
|
lp3->l_fp = lp2; /* Link in */
|
|
lp2->l_fp = lp1;
|
|
lp1->l_bp = lp2;
|
|
lp2->l_bp = lp3;
|
|
for (i=0; i<n; ++i)
|
|
lp2->l_text[i] = c;
|
|
curwp->w_dotp = lp2;
|
|
curwp->w_doto = n;
|
|
return (TRUE);
|
|
}
|
|
doto = curwp->w_doto; /* Save for later. */
|
|
if (lp1->l_used+n > lp1->l_size) { /* Hard: reallocate */
|
|
if ((lp2=lalloc(lp1->l_used+n)) == NULL)
|
|
return (FALSE);
|
|
cp1 = &lp1->l_text[0];
|
|
cp2 = &lp2->l_text[0];
|
|
while (cp1 != &lp1->l_text[doto])
|
|
*cp2++ = *cp1++;
|
|
cp2 += n;
|
|
while (cp1 != &lp1->l_text[lp1->l_used])
|
|
*cp2++ = *cp1++;
|
|
lp1->l_bp->l_fp = lp2;
|
|
lp2->l_fp = lp1->l_fp;
|
|
lp1->l_fp->l_bp = lp2;
|
|
lp2->l_bp = lp1->l_bp;
|
|
free((char *) lp1);
|
|
} else { /* Easy: in place */
|
|
lp2 = lp1; /* Pretend new line */
|
|
lp2->l_used += n;
|
|
cp2 = &lp1->l_text[lp1->l_used];
|
|
cp1 = cp2-n;
|
|
while (cp1 != &lp1->l_text[doto])
|
|
*--cp2 = *--cp1;
|
|
}
|
|
for (i=0; i<n; ++i) /* Add the characters */
|
|
lp2->l_text[doto+i] = c;
|
|
wp = wheadp; /* Update windows */
|
|
while (wp != NULL) {
|
|
if (wp->w_linep == lp1)
|
|
wp->w_linep = lp2;
|
|
if (wp->w_dotp == lp1) {
|
|
wp->w_dotp = lp2;
|
|
if (wp==curwp || wp->w_doto>doto)
|
|
wp->w_doto += n;
|
|
}
|
|
if (wp->w_markp == lp1) {
|
|
wp->w_markp = lp2;
|
|
if (wp->w_marko > doto)
|
|
wp->w_marko += n;
|
|
}
|
|
wp = wp->w_wndp;
|
|
}
|
|
return (TRUE);
|
|
}
|
|
|
|
/*
|
|
* Insert a newline into the buffer
|
|
* at the current location of dot in the current
|
|
* window. The funny ass-backwards way it does things
|
|
* is not a botch; it just makes the last line in
|
|
* the file not a special case. Return TRUE if everything
|
|
* works out and FALSE on error (memory allocation
|
|
* failure). The update of dot and mark is a bit
|
|
* easier then in the above case, because the split
|
|
* forces more updating.
|
|
*/
|
|
lnewline()
|
|
{
|
|
register char *cp1;
|
|
register char *cp2;
|
|
register LINE *lp1;
|
|
register LINE *lp2;
|
|
register int doto;
|
|
register WINDOW *wp;
|
|
|
|
lchange(WFHARD);
|
|
lp1 = curwp->w_dotp; /* Get the address and */
|
|
doto = curwp->w_doto; /* offset of "." */
|
|
if ((lp2=lalloc(doto)) == NULL) /* New first half line */
|
|
return (FALSE);
|
|
cp1 = &lp1->l_text[0]; /* Shuffle text around */
|
|
cp2 = &lp2->l_text[0];
|
|
while (cp1 != &lp1->l_text[doto])
|
|
*cp2++ = *cp1++;
|
|
cp2 = &lp1->l_text[0];
|
|
while (cp1 != &lp1->l_text[lp1->l_used])
|
|
*cp2++ = *cp1++;
|
|
lp1->l_used -= doto;
|
|
lp2->l_bp = lp1->l_bp;
|
|
lp1->l_bp = lp2;
|
|
lp2->l_bp->l_fp = lp2;
|
|
lp2->l_fp = lp1;
|
|
wp = wheadp; /* Windows */
|
|
while (wp != NULL) {
|
|
if (wp->w_linep == lp1)
|
|
wp->w_linep = lp2;
|
|
if (wp->w_dotp == lp1) {
|
|
if (wp->w_doto < doto)
|
|
wp->w_dotp = lp2;
|
|
else
|
|
wp->w_doto -= doto;
|
|
}
|
|
if (wp->w_markp == lp1) {
|
|
if (wp->w_marko < doto)
|
|
wp->w_markp = lp2;
|
|
else
|
|
wp->w_marko -= doto;
|
|
}
|
|
wp = wp->w_wndp;
|
|
}
|
|
return (TRUE);
|
|
}
|
|
|
|
/*
|
|
* This function deletes "n" bytes,
|
|
* starting at dot. It understands how do deal
|
|
* with end of lines, etc. It returns TRUE if all
|
|
* of the characters were deleted, and FALSE if
|
|
* they were not (because dot ran into the end of
|
|
* the buffer. The "kflag" is TRUE if the text
|
|
* should be put in the kill buffer.
|
|
*/
|
|
ldelete(n, kflag)
|
|
{
|
|
register char *cp1;
|
|
register char *cp2;
|
|
register LINE *dotp;
|
|
register int doto;
|
|
register int chunk;
|
|
register WINDOW *wp;
|
|
|
|
while (n != 0) {
|
|
dotp = curwp->w_dotp;
|
|
doto = curwp->w_doto;
|
|
if (dotp == curbp->b_linep) /* Hit end of buffer. */
|
|
return (FALSE);
|
|
chunk = dotp->l_used-doto; /* Size of chunk. */
|
|
if (chunk > n)
|
|
chunk = n;
|
|
if (chunk == 0) { /* End of line, merge. */
|
|
lchange(WFHARD);
|
|
if (ldelnewline() == FALSE
|
|
|| (kflag!=FALSE && kinsert('\n')==FALSE))
|
|
return (FALSE);
|
|
--n;
|
|
continue;
|
|
}
|
|
lchange(WFEDIT);
|
|
cp1 = &dotp->l_text[doto]; /* Scrunch text. */
|
|
cp2 = cp1 + chunk;
|
|
if (kflag != FALSE) { /* Kill? */
|
|
while (cp1 != cp2) {
|
|
if (kinsert(*cp1) == FALSE)
|
|
return (FALSE);
|
|
++cp1;
|
|
}
|
|
cp1 = &dotp->l_text[doto];
|
|
}
|
|
while (cp2 != &dotp->l_text[dotp->l_used])
|
|
*cp1++ = *cp2++;
|
|
dotp->l_used -= chunk;
|
|
wp = wheadp; /* Fix windows */
|
|
while (wp != NULL) {
|
|
if (wp->w_dotp==dotp && wp->w_doto>=doto) {
|
|
wp->w_doto -= chunk;
|
|
if (wp->w_doto < doto)
|
|
wp->w_doto = doto;
|
|
}
|
|
if (wp->w_markp==dotp && wp->w_marko>=doto) {
|
|
wp->w_marko -= chunk;
|
|
if (wp->w_marko < doto)
|
|
wp->w_marko = doto;
|
|
}
|
|
wp = wp->w_wndp;
|
|
}
|
|
n -= chunk;
|
|
}
|
|
return (TRUE);
|
|
}
|
|
|
|
/*
|
|
* Delete a newline. Join the current line
|
|
* with the next line. If the next line is the magic
|
|
* header line always return TRUE; merging the last line
|
|
* with the header line can be thought of as always being a
|
|
* successful operation, even if nothing is done, and this makes
|
|
* the kill buffer work "right". Easy cases can be done by
|
|
* shuffling data around. Hard cases require that lines be moved
|
|
* about in memory. Return FALSE on error and TRUE if all
|
|
* looks ok. Called by "ldelete" only.
|
|
*/
|
|
ldelnewline()
|
|
{
|
|
register char *cp1;
|
|
register char *cp2;
|
|
register LINE *lp1;
|
|
register LINE *lp2;
|
|
register LINE *lp3;
|
|
register WINDOW *wp;
|
|
|
|
lp1 = curwp->w_dotp;
|
|
lp2 = lp1->l_fp;
|
|
if (lp2 == curbp->b_linep) { /* At the buffer end. */
|
|
if (lp1->l_used == 0) /* Blank line. */
|
|
lfree(lp1);
|
|
return (TRUE);
|
|
}
|
|
if (lp2->l_used <= lp1->l_size-lp1->l_used) {
|
|
cp1 = &lp1->l_text[lp1->l_used];
|
|
cp2 = &lp2->l_text[0];
|
|
while (cp2 != &lp2->l_text[lp2->l_used])
|
|
*cp1++ = *cp2++;
|
|
wp = wheadp;
|
|
while (wp != NULL) {
|
|
if (wp->w_linep == lp2)
|
|
wp->w_linep = lp1;
|
|
if (wp->w_dotp == lp2) {
|
|
wp->w_dotp = lp1;
|
|
wp->w_doto += lp1->l_used;
|
|
}
|
|
if (wp->w_markp == lp2) {
|
|
wp->w_markp = lp1;
|
|
wp->w_marko += lp1->l_used;
|
|
}
|
|
wp = wp->w_wndp;
|
|
}
|
|
lp1->l_used += lp2->l_used;
|
|
lp1->l_fp = lp2->l_fp;
|
|
lp2->l_fp->l_bp = lp1;
|
|
free((char *) lp2);
|
|
return (TRUE);
|
|
}
|
|
if ((lp3=lalloc(lp1->l_used+lp2->l_used)) == NULL)
|
|
return (FALSE);
|
|
cp1 = &lp1->l_text[0];
|
|
cp2 = &lp3->l_text[0];
|
|
while (cp1 != &lp1->l_text[lp1->l_used])
|
|
*cp2++ = *cp1++;
|
|
cp1 = &lp2->l_text[0];
|
|
while (cp1 != &lp2->l_text[lp2->l_used])
|
|
*cp2++ = *cp1++;
|
|
lp1->l_bp->l_fp = lp3;
|
|
lp3->l_fp = lp2->l_fp;
|
|
lp2->l_fp->l_bp = lp3;
|
|
lp3->l_bp = lp1->l_bp;
|
|
wp = wheadp;
|
|
while (wp != NULL) {
|
|
if (wp->w_linep==lp1 || wp->w_linep==lp2)
|
|
wp->w_linep = lp3;
|
|
if (wp->w_dotp == lp1)
|
|
wp->w_dotp = lp3;
|
|
else if (wp->w_dotp == lp2) {
|
|
wp->w_dotp = lp3;
|
|
wp->w_doto += lp1->l_used;
|
|
}
|
|
if (wp->w_markp == lp1)
|
|
wp->w_markp = lp3;
|
|
else if (wp->w_markp == lp2) {
|
|
wp->w_markp = lp3;
|
|
wp->w_marko += lp1->l_used;
|
|
}
|
|
wp = wp->w_wndp;
|
|
}
|
|
free((char *) lp1);
|
|
free((char *) lp2);
|
|
return (TRUE);
|
|
}
|
|
|
|
/*
|
|
* Delete all of the text
|
|
* saved in the kill buffer. Called by commands
|
|
* when a new kill context is being created. The kill
|
|
* buffer array is released, just in case the buffer has
|
|
* grown to immense size. No errors.
|
|
*/
|
|
kdelete()
|
|
{
|
|
if (kbufp != NULL) {
|
|
free((char *) kbufp);
|
|
kbufp = NULL;
|
|
kused = 0;
|
|
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.
|
|
*/
|
|
kinsert(c)
|
|
{
|
|
register char *nbufp;
|
|
register int i;
|
|
|
|
if (kused == ksize) {
|
|
if ((nbufp=malloc(ksize+KBLOCK)) == NULL)
|
|
return (FALSE);
|
|
for (i=0; i<ksize; ++i)
|
|
nbufp[i] = kbufp[i];
|
|
if (kbufp != NULL)
|
|
free((char *) kbufp);
|
|
kbufp = nbufp;
|
|
ksize += KBLOCK;
|
|
}
|
|
kbufp[kused++] = c;
|
|
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.
|
|
*/
|
|
kremove(n)
|
|
{
|
|
if (n >= kused)
|
|
return (-1);
|
|
else
|
|
return (kbufp[n] & 0xFF);
|
|
}
|