home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Frozen Fish 1: Amiga
/
FrozenFish-Apr94.iso
/
bbs
/
alib
/
d3xx
/
d352
/
mg.lha
/
MG
/
src.LZH
/
mg
/
line.c
< prev
next >
Wrap
C/C++ Source or Header
|
1990-05-23
|
20KB
|
743 lines
/*
* Text line handling. 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 "def.h"
#include "line.h"
#include "buffer.h"
#include "window.h"
#ifdef ANSI
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
/* Use the ANSI offsetof if we can */
#define OFFSET(t, m) offsetof(t, m)
#else
/* Otherwise, if we don't know yet, use the default */
#ifndef OFFSET
#define OFFSET(type,member) ((char *)&(((type *)0)->member)-(char *)((type *)0))
#endif
#endif
/*
* define/prototype static functions here.
*/
static VOID kcopy
PROTO((char *, RSIZE, int));
static kgrow
PROTO((int));
#ifndef NBLOCK
#define NBLOCK 16 /* Line block chunk size */
#endif
#define KBLOCK 256
#define KBSHIFT 8 /* = log[2](KBLOCK) */
#define KBSIZE 2048 /* maximal size of kbbuf array */
static char **kbufp = NULL; /* now addressed as an array of arrays */
static RSIZE kallst = 0; /* # of first allocated byte */
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. */
/*
* This routine allocates a block of memory large enough to hold a LINE
* containing "used" characters. The block is rounded up to whatever needs to
* be allocated. (use lallocx for lines likely to grow.) 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.
*/
struct line *
lalloc(used)
register int used;
{
register struct line *lp;
register int size;
/* any padding at the end of the structure is used */
if ((size = used + OFFSET(struct line, l_text[0])) < sizeof(struct line))
size = sizeof(struct line);
#ifdef MALLOCROUND
MALLOCROUND(size); /* round up to a size optimal to malloc */
#endif
if ((lp = (struct line *) malloc((unsigned) size)) == NULL) {
ewprintf("Can't get %d bytes", size);
return (struct line *) NULL;
}
lp->l_size = size - OFFSET(struct line, l_text[0]);
lp->l_used = used;
return lp;
}
/*
* Like lalloc, only round amount desired up because this line will probably
* grow. We always make room for at least one more char. (thus making 0 not
* a special case anymore.)
*/
struct line *
lallocx(used)
int used;
{
register int size;
register struct line *lp;
size = (NBLOCK + used) & ~(NBLOCK - 1);
if ((lp = lalloc(size)) != NULL)
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.
*/
VOID
lfree(lp)
register struct line *lp;
{
register struct buffer *bp;
register struct window *wp;
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
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;
}
}
for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
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;
}
}
}
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).
*/
VOID
lchange(flag)
register int flag;
{
register struct window *wp;
if ((curbp->b_flag & BFCHG) == 0) { /* First change, so */
flag |= WFMODE; /* update mode lines. */
curbp->b_flag |= BFCHG;
}
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
if (wp->w_bufp == curbp) {
wp->w_flag |= flag;
if (wp != curwp)
wp->w_flag |= WFHARD;
}
}
}
/*
* 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)
int n;
{
register char *cp1;
register char *cp2;
register struct line *lp1;
struct line *lp2;
struct line *lp3;
register int doto;
register RSIZE i;
struct window *wp;
lchange(WFEDIT);
lp1 = curwp->w_dotp; /* Current line */
if (lp1 == curbp->b_linep) { /* At the end: special */
/* (now should only happen in empty buffer */
if (curwp->w_doto != 0) {
ewprintf("bug: linsert");
return FALSE;
}
if ((lp2 = lallocx(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;
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
if (wp->w_linep == lp1)
wp->w_linep = lp2;
if (wp->w_dotp == lp1)
wp->w_dotp = lp2;
if (wp->w_markp == lp1)
wp->w_markp = lp2;
}
/* NOSTRICT */
curwp->w_doto = n;
return TRUE;
}
doto = curwp->w_doto; /* Save for later. */
/* NOSTRICT (2) */
if (lp1->l_used + n > lp1->l_size) { /* Hard: reallocate */
if ((lp2 = lallocx(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++;
/* NOSTRICT */
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 */
/* NOSTRICT */
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;
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
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)
/* NOSTRICT */
wp->w_doto += n;
}
if (wp->w_markp == lp1) {
wp->w_markp = lp2;
if (wp->w_marko > doto)
/* NOSTRICT */
wp->w_marko += n;
}
}
return TRUE;
}
/*
* Insert a newline into the buffer at the current location of dot in the
* current window. The funny ass-backwards way is no longer used.
*/
lnewline()
{
register struct line *lp1;
register struct line *lp2;
register int doto;
register int nlen;
struct window *wp;
lchange(WFHARD);
lp1 = curwp->w_dotp; /* Get the address and */
doto = curwp->w_doto; /* offset of "." */
if (doto == 0) { /* avoid unnessisary copying */
if ((lp2 = lallocx(0)) == NULL) /* new first part */
return FALSE;
lp2->l_bp = lp1->l_bp;
lp1->l_bp->l_fp = lp2;
lp2->l_fp = lp1;
lp1->l_bp = lp2;
if (lp1->l_bp == lp1->l_fp) /* Screwy case */
for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
if (wp->w_linep == lp1)
wp->w_linep = lp2;
return TRUE;
}
nlen = llength(lp1) - doto; /* length of new part */
if ((lp2 = lallocx(nlen)) == NULL) /* New second half line */
return FALSE;
if (nlen != 0)
bcopy(&lp1->l_text[doto], &lp2->l_text[0], nlen);
lp1->l_used = doto;
lp2->l_bp = lp1;
lp2->l_fp = lp1->l_fp;
lp1->l_fp = lp2;
lp2->l_fp->l_bp = lp2;
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { /* Windows */
if (wp->w_dotp == lp1 && wp->w_doto >= doto) {
wp->w_dotp = lp2;
wp->w_doto -= doto;
}
if (wp->w_markp == lp1 && wp->w_marko >= doto) {
wp->w_markp = lp2;
wp->w_marko -= doto;
}
}
return TRUE;
}
/*
* This function copies a string of length N into the kill buffer starting at
* index X.
*/
static VOID
kcopy(str, x, n)
register char *str;
register RSIZE x;
register int n;
{
register int i;
for (i = 0; i < n; i++) {
kbufp[x >> KBSHIFT][x & (KBLOCK - 1)] = *str;
str++;
x++;
}
}
/*
* This function deletes "n" bytes forward from 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. Kflag is true if we instert the deleted character into the kill
* buffer.
*/
fdelete(n, kflag)
RSIZE n;
{
register char *cp1;
register char *cp2;
register struct line *dotp;
register int doto;
register RSIZE chunk;
struct 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. */
if (dotp == lback(curbp->b_linep))
return FALSE; /* End of buffer. */
lchange(WFHARD);
if (ldelnewline() == FALSE
|| (kflag && kinsert('\n', KFORW) == FALSE))
return FALSE;
--n;
continue;
}
lchange(WFEDIT);
cp1 = &dotp->l_text[doto]; /* Scrunch text. */
cp2 = cp1 + chunk;
if (kflag) {
while (ksize - kused < chunk)
if (kgrow(FALSE) == FALSE)
return FALSE;
kcopy(cp1, kallst + kused, (int) chunk);
kused += chunk;
}
while (cp2 != &dotp->l_text[dotp->l_used])
*cp1++ = *cp2++;
dotp->l_used -= (int) chunk;
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
if (wp->w_dotp == dotp && wp->w_doto >= doto) {
/* NOSTRICT */
wp->w_doto -= chunk;
if (wp->w_doto < doto)
wp->w_doto = doto;
}
if (wp->w_markp == dotp && wp->w_marko >= doto) {
/* NOSTRICT */
wp->w_marko -= chunk;
if (wp->w_marko < doto)
wp->w_marko = doto;
}
}
n -= chunk;
}
return TRUE;
}
/*
* This function deletes "n" bytes backwards from 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. Kflag is true if we instert the deleted character into the kill
* buffer.
*/
bdelete(n, kflag)
RSIZE n;
{
register char *cp1;
register char *cp2;
register struct line *dotp;
register int doto;
register RSIZE chunk;
struct 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 = doto; /* Size of chunk. */
if (chunk > n)
chunk = n;
if (chunk == 0) { /* End of line, merge. */
if (backchar(FFRAND, 1) == FALSE)
return FALSE; /* End of buffer. */
lchange(WFHARD);
if (ldelnewline() == FALSE
|| (kflag && kinsert('\n', KBACK) == FALSE))
return FALSE;
--n;
continue;
}
lchange(WFEDIT);
cp2 = &dotp->l_text[doto]; /* Scrunch text. */
cp1 = cp2 - chunk;
if (kflag) {
while (kstart - kallst < chunk)
if (kgrow(TRUE) == FALSE)
return FALSE;
kcopy(cp1, kstart - chunk, (int) chunk);
kstart -= chunk;
}
while (cp2 != &dotp->l_text[dotp->l_used])
*cp1++ = *cp2++;
dotp->l_used -= (int) chunk;
doto -= chunk;
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
if (wp->w_dotp == dotp && wp->w_doto >= doto) {
/* NOSTRICT */
wp->w_doto -= chunk;
if (wp->w_doto < doto)
wp->w_doto = doto;
}
if (wp->w_markp == dotp && wp->w_marko >= doto) {
/* NOSTRICT */
wp->w_marko -= chunk;
if (wp->w_marko < doto)
wp->w_marko = doto;
}
}
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.
*/
ldelnewline()
{
register struct line *lp1;
register struct line *lp2;
register struct window *wp;
struct line *lp3;
lp1 = curwp->w_dotp;
lp2 = lp1->l_fp;
if (lp2 == curbp->b_linep) /* At the buffer end. */
return TRUE;
if (lp2->l_used <= lp1->l_size - lp1->l_used) {
bcopy(&lp2->l_text[0], &lp1->l_text[lp1->l_used], lp2->l_used);
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
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;
}
}
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;
bcopy(&lp1->l_text[0], &lp3->l_text[0], lp1->l_used);
bcopy(&lp2->l_text[0], &lp3->l_text[lp1->l_used], lp2->l_used);
lp1->l_bp->l_fp = lp3;
lp3->l_fp = lp2->l_fp;
lp2->l_fp->l_bp = lp3;
lp3->l_bp = lp1->l_bp;
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
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;
}
}
free((char *) lp1);
free((char *) lp2);
return TRUE;
}
/*
* Replace plen characters before dot with argument string. Control-J
* characters in st are interpreted as newlines. There is a casehack disable
* flag (normally it likes to match case of replacement to what was there).
*/
lreplace(plen, st, f)
register RSIZE plen; /* length to remove */
char *st; /* replacement string */
int f; /* case hack disable */
{
register RSIZE rlen; /* replacement length */
register int rtype; /* capitalization */
register int c; /* used for random characters */
register int doto; /* offset into line */
/*
* Find the capitalization of the word that was found. f says use
* exact case of replacement string (same thing that happens with
* lowercase found), so bypass check.
*/
/* NOSTRICT */
(VOID) backchar(FFARG | FFRAND, (int) plen);
rtype = _L;
c = lgetc(curwp->w_dotp, curwp->w_doto);
if (ISUPPER(c) != FALSE && f == FALSE) {
rtype = _U | _L;
if (curwp->w_doto + 1 < llength(curwp->w_dotp)) {
c = lgetc(curwp->w_dotp, curwp->w_doto + 1);
if (ISUPPER(c) != FALSE) {
rtype = _U;
}
}
}
/*
* make the string lengths match (either pad the line so that it will
* fit, or scrunch out the excess). be careful with dot's offset.
*/
rlen = st ? strlen(st) : 0;
doto = curwp->w_doto;
if (plen > rlen)
(VOID) fdelete((RSIZE) (plen - rlen), FALSE);
else if (plen < rlen) {
if (linsert((int) (rlen - plen), ' ') == FALSE)
return FALSE;
}
curwp->w_doto = doto;
/*
* do the replacement: If was capital, then place first char as if
* upper, and subsequent chars as if lower. If inserting upper, check
* replacement for case.
*/
while ((c = CHARMASK(*st++)) != '\0') {
if ((rtype & _U) != 0 && ISLOWER(c) != 0)
c = TOUPPER(c);
if (rtype == (_U | _L))
rtype = _L;
if (c == CCHR('J')) {
if (curwp->w_doto == llength(curwp->w_dotp))
(VOID) forwchar(FFRAND, 1);
else {
if (fdelete((RSIZE) 1, FALSE) != FALSE)
(VOID) lnewline();
}
} else if (curwp->w_dotp == curbp->b_linep) {
(VOID) linsert(1, c);
} else if (curwp->w_doto == llength(curwp->w_dotp)) {
if (fdelete((RSIZE) 1, FALSE) != FALSE)
(VOID) linsert(1, c);
} else
lputc(curwp->w_dotp, curwp->w_doto++, c);
}
lchange(WFHARD);
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.
*/
VOID
kdelete()
{
int i;
if (kbufp != NULL)
for (i = 0; i < KBSIZE; i++)
if (kbufp[i] != NULL) {
free((char *) kbufp[i]);
kbufp[i] = NULL;
}
kstart = kused = ksize = kallst = 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.
*/
kinsert(c, dir)
{
if (kused == ksize && dir == KFORW && kgrow(FALSE) == FALSE)
return FALSE;
if (kstart == kallst && dir == KBACK && kgrow(TRUE) == FALSE)
return FALSE;
if (dir == KFORW) {
kbufp[(kallst + kused) >> KBSHIFT][(kallst + kused) & (KBLOCK - 1)] = c;
kused++;
} else if (dir == KBACK) {
kstart--;
kbufp[kstart >> KBSHIFT][kstart & (KBLOCK - 1)] = c;
} else
panic("broken kinsert call"); /* Oh shit! */
return (TRUE);
}
/*
* kgrow - just get more kill buffer for the callee. back is true if we are
* trying to get space at the beginning of the kill buffer.
*/
static
kgrow(back)
{
register char *nbufp;
register int i;
if (kbufp == NULL)
if ((kbufp = (char **) malloc(KBSIZE * sizeof(nbufp))) == NULL) {
ewprintf("Can't get %d bytes", KBSIZE * sizeof(nbufp));
return (FALSE);
} else
for (i = 0; i < KBSIZE; i++)
kbufp[i] = NULL;
if ((kallst == 0) && (kused == 0))
kallst = kstart = ((long) KBSIZE / 2) * KBLOCK;
if ((back && (kallst == 0)) ||
(!back && ((kallst + kused) == ((long) KBSIZE - 1) * KBLOCK))) {
ewprintf("Kill buffer overflow!");
return (FALSE);
}
if ((nbufp = (char *) malloc(KBLOCK)) == NULL) {
ewprintf("Can't get %d bytes", KBLOCK);
return (FALSE);
}
if (back) {
kallst -= KBLOCK;
kused += KBLOCK;
kbufp[kallst >> KBSHIFT] = nbufp;
} else
kbufp[(kallst + ksize) >> KBSHIFT] = nbufp;
ksize += KBLOCK;
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.
*/
char
kremove(n)
register RSIZE n;
{
register RSIZE addr;
addr = (n + kstart);
if ((n < 0) || (addr >= (kused + kallst)))
return -1;
return CHARMASK(kbufp[addr >> KBSHIFT][addr & (KBLOCK - 1)]);
}
/*
* This function gets entire lines from the kill buffer. If there is no line,
* a "-1" is returned, otherwise n is advanced, the characters are copied to
* str and the length is returned.
*/
int
klremove(str, n)
char *str;
RSIZE *n;
{
register int size;
register RSIZE addr;
size = 0;
addr = kstart + *n;
while ((addr >= kstart) && (addr < (kallst + kused)) &&
((*str++ = kbufp[addr >> KBSHIFT][addr & (KBLOCK - 1)]) != '\n')) {
addr++;
size++;
}
if ((addr >= kstart) && (addr < (kallst + kused))) {
*n = addr - kstart;
return (size);
} else
return -1;
}