diff options
Diffstat (limited to 'usr.bin/mg/line.c')
-rw-r--r-- | usr.bin/mg/line.c | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/usr.bin/mg/line.c b/usr.bin/mg/line.c new file mode 100644 index 00000000000..3c892c706b1 --- /dev/null +++ b/usr.bin/mg/line.c @@ -0,0 +1,609 @@ +/* + * 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" + +/* number of bytes member is from start of structure type */ +/* should be computed at compile time */ + +#ifndef OFFSET +#define OFFSET(type,member) ((char *)&(((type *)0)->member)-(char *)((type *)0)) +#endif + +#ifndef NBLOCK +#define NBLOCK 16 /* Line block chunk size */ +#endif + +#ifndef KBLOCK +#define KBLOCK 256 /* Kill buffer block size. */ +#endif + +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. */ + +/* + * 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. + */ +LINE * +lalloc(used) register int used; { + register LINE *lp; + register int size; + + /* any padding at the end of the structure is used */ + if((size = used + OFFSET(LINE, l_text[0])) < sizeof(LINE)) + size = sizeof(LINE); +#ifdef MALLOCROUND + MALLOCROUND(size); /* round up to a size optimal to malloc */ +#endif + if((lp = (LINE *)malloc((unsigned)size)) == NULL) { + ewprintf("Can't get %d bytes", size); + return (LINE *)NULL; + } + lp->l_size = size - OFFSET(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.) + */ +LINE * +lallocx(used) +int used; +{ + register int size; + register 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 LINE *lp; { + register BUFFER *bp; + register 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 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 LINE *lp1; + LINE *lp2; + LINE *lp3; + register int doto; + register RSIZE i; + 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 LINE *lp1; + register LINE *lp2; + register int doto; + register int nlen; + 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; + 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 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" indicates either no insertion, + * or direction of insertion into the kill buffer. + */ +ldelete(n, kflag) RSIZE n; { + register char *cp1; + register char *cp2; + register LINE *dotp; + register int doto; + register RSIZE chunk; + WINDOW *wp; + + /* + * HACK - doesn't matter, and fixes back-over-nl bug for empty + * kill buffers. + */ + if (kused == kstart) kflag = KFORW; + + 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!=KNONE && kinsert('\n', kflag)==FALSE)) + return FALSE; + --n; + continue; + } + lchange(WFEDIT); + cp1 = &dotp->l_text[doto]; /* Scrunch text. */ + cp2 = cp1 + chunk; + if (kflag == KFORW) { + while (ksize - kused < chunk) + if (kgrow(FALSE) == FALSE) return FALSE; + bcopy(cp1, &(kbufp[kused]), (int) chunk); + kused += chunk; + } else if (kflag == KBACK) { + while (kstart < chunk) + if (kgrow(TRUE) == FALSE) return FALSE; + bcopy(cp1, &(kbufp[kstart-chunk]), (int) chunk); + kstart -= chunk; + } else if (kflag != KNONE) panic("broken ldelete call"); + 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; +} + +/* + * 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 LINE *lp1; + register LINE *lp2; + register WINDOW *wp; + 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 = strlen(st); + doto = curwp->w_doto; + if (plen > rlen) + (VOID) ldelete((RSIZE) (plen-rlen), KNONE); + 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 (ldelete((RSIZE) 1, KNONE) != 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 (ldelete((RSIZE) 1, KNONE) != 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() { + if (kbufp != NULL) { + free((char *) 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. + */ +kinsert(c, dir) { + + if (kused == ksize && dir == KFORW && kgrow(FALSE) == FALSE) + return FALSE; + if (kstart == 0 && dir == KBACK && kgrow(TRUE) == 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. back is true if + * we are trying to get space at the beginning of the kill buffer. + */ +kgrow(back) { + register int nstart; + register char *nbufp; + + if ((unsigned)(ksize+KBLOCK) <= (unsigned)ksize) { + /* probably 16 bit unsigned */ + ewprintf("Kill buffer size at maximum"); + return FALSE; + } + if ((nbufp=malloc((unsigned)(ksize+KBLOCK))) == NULL) { + ewprintf("Can't get %ld bytes", (long)(ksize+KBLOCK)); + return FALSE; + } + nstart = (back == TRUE) ? (kstart + KBLOCK) : (KBLOCK / 4) ; + bcopy(&(kbufp[kstart]), &(nbufp[nstart]), (int) (kused-kstart)); + if (kbufp != NULL) + free((char *) 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. + */ +kremove(n) { + if (n < 0 || n + kstart >= kused) + return -1; + return CHARMASK(kbufp[n + kstart]); +} |