/* $OpenBSD: line.c,v 1.61 2018/08/29 07:50:16 reyk Exp $ */ /* This file is in the public domain. */ /* * 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. * * 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 displayed, which means that "b_nwnd" is non-zero, * which means that the dot and mark values in the buffer headers are * nonsense. */ #include #include #include #include #include #include #include #include "def.h" int casereplace = TRUE; /* * Preserve the case of the replaced string. */ int setcasereplace(int f, int n) { if (f & FFARG) casereplace = n > 0; else casereplace = !casereplace; ewprintf("Case-replace is %sabled", casereplace ? "en" : "dis"); return (TRUE); } /* * Allocate a new line of size `used'. lrealloc() can be called if the line * ever needs to grow beyond that. */ struct line * lalloc(int used) { struct line *lp; if ((lp = malloc(sizeof(*lp))) == NULL) return (NULL); lp->l_text = NULL; lp->l_size = 0; lp->l_used = used; /* XXX */ if (lrealloc(lp, used) == FALSE) { free(lp); return (NULL); } return (lp); } int lrealloc(struct line *lp, int newsize) { char *tmp; if (lp->l_size < newsize) { if ((tmp = realloc(lp->l_text, newsize)) == NULL) return (FALSE); lp->l_text = tmp; lp->l_size = newsize; } return (TRUE); } /* * Delete line "lp". Fix all of the links that might point to it (they are * moved to offset 0 of the next line. Unlink the line from whatever buffer * it might be in, and release the memory. The buffers are updated too; the * magic conditions described in the above comments don't hold here. */ void lfree(struct line *lp) { struct buffer *bp; struct mgwin *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(lp->l_text); free(lp); } /* * This routine is called when a character changes 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(int flag) { struct mgwin *wp; /* update mode lines if this is the first change. */ if ((curbp->b_flag & BFCHG) == 0) { flag |= WFMODE; curbp->b_flag |= BFCHG; } for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { if (wp->w_bufp == curbp) { wp->w_rflag |= flag; if (wp != curwp) wp->w_rflag |= WFFULL; } } } /* * 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. */ int linsert(int n, int c) { struct line *lp1; struct mgwin *wp; RSIZE i; int doto; int s; if (!n) return (TRUE); if ((s = checkdirty(curbp)) != TRUE) return (s); if (curbp->b_flag & BFREADONLY) { dobeep(); ewprintf("Buffer is read only"); return (FALSE); } lchange(WFEDIT); /* current line */ lp1 = curwp->w_dotp; /* special case for the end */ if (lp1 == curbp->b_headp) { struct line *lp2, *lp3; /* now should only happen in empty buffer */ if (curwp->w_doto != 0) { dobeep(); ewprintf("bug: linsert"); return (FALSE); } /* allocate a new line */ if ((lp2 = lalloc(n)) == NULL) return (FALSE); /* previous line */ lp3 = lp1->l_bp; /* link in */ lp3->l_fp = lp2; 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; } undo_add_insert(lp2, 0, n); curwp->w_doto = n; return (TRUE); } /* save for later */ doto = curwp->w_doto; if ((lp1->l_used + n) > lp1->l_size) { if (lrealloc(lp1, lp1->l_used + n) == FALSE) return (FALSE); } lp1->l_used += n; if (lp1->l_used != n) memmove(&lp1->l_text[doto + n], &lp1->l_text[doto], lp1->l_used - n - doto); /* Add the characters */ for (i = 0; i < n; ++i) lp1->l_text[doto + i] = c; for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { if (wp->w_dotp == lp1) { if (wp == curwp || wp->w_doto > doto) wp->w_doto += n; } if (wp->w_markp == lp1) { if (wp->w_marko > doto) wp->w_marko += n; } } undo_add_insert(curwp->w_dotp, doto, n); return (TRUE); } /* * Do the work of inserting a newline at the given line/offset. * If mark is on the current line, we may have to move the markline * to keep line numbers in sync. * lnewline_at assumes the current buffer is writable. Checking for * this fact should be done by the caller. */ int lnewline_at(struct line *lp1, int doto) { struct line *lp2; struct mgwin *wp; int nlen, tcurwpdotline; lchange(WFFULL); curwp->w_bufp->b_lines++; /* Check if mark is past dot (even on current line) */ if (curwp->w_markline > curwp->w_dotline || (curwp->w_dotline == curwp->w_markline && curwp->w_marko >= doto)) curwp->w_markline++; tcurwpdotline = curwp->w_dotline; /* If start of line, allocate a new line instead of copying */ if (doto == 0) { /* new first part */ if ((lp2 = lalloc(0)) == NULL) 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; if (wp->w_dotline >= tcurwpdotline && wp->w_bufp == curwp->w_bufp) wp->w_dotline++; } undo_add_boundary(FFRAND, 1); undo_add_insert(lp2, 0, 1); undo_add_boundary(FFRAND, 1); return (TRUE); } /* length of new part */ nlen = llength(lp1) - doto; /* new second half line */ if ((lp2 = lalloc(nlen)) == NULL) 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; /* Windows */ for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { if (wp->w_dotp == lp1 && wp->w_doto >= doto) { wp->w_dotp = lp2; wp->w_doto -= doto; wp->w_dotline++; } else if (wp->w_dotline > tcurwpdotline && wp->w_bufp == curwp->w_bufp) wp->w_dotline++; if (wp->w_markp == lp1 && wp->w_marko >= doto) { wp->w_markp = lp2; wp->w_marko -= doto; } } undo_add_boundary(FFRAND, 1); undo_add_insert(lp1, llength(lp1), 1); undo_add_boundary(FFRAND, 1); return (TRUE); } /* * Insert a newline into the buffer at the current location of dot in the * current window. */ int lnewline(void) { int s; if ((s = checkdirty(curbp)) != TRUE) return (s); if (curbp->b_flag & BFREADONLY) { dobeep(); ewprintf("Buffer is read only"); return (FALSE); } return (lnewline_at(curwp->w_dotp, curwp->w_doto)); } /* * This function deletes "n" bytes, starting at dot. (actually, n+1, as the * newline is included) It understands how to 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. */ int ldelete(RSIZE n, int kflag) { struct line *dotp; RSIZE chunk; struct mgwin *wp; int doto; char *cp1, *cp2; size_t len; char *sv = NULL; int end; int s; int rval = FALSE; if ((s = checkdirty(curbp)) != TRUE) return (s); if (curbp->b_flag & BFREADONLY) { dobeep(); ewprintf("Buffer is read only"); goto out; } len = n; if ((sv = calloc(1, len + 1)) == NULL) goto out; end = 0; undo_add_delete(curwp->w_dotp, curwp->w_doto, n, (kflag & KREG)); while (n != 0) { dotp = curwp->w_dotp; doto = curwp->w_doto; /* Hit the end of the buffer */ if (dotp == curbp->b_headp) goto out; /* Size of the chunk */ chunk = dotp->l_used - doto; if (chunk > n) chunk = n; /* End of line, merge */ if (chunk == 0) { if (dotp == blastlp(curbp)) goto out; lchange(WFFULL); if (ldelnewline() == FALSE) goto out; end = strlcat(sv, "\n", len + 1); --n; continue; } lchange(WFEDIT); /* Scrunch text */ cp1 = &dotp->l_text[doto]; memcpy(&sv[end], cp1, chunk); end += chunk; sv[end] = '\0'; for (cp2 = cp1 + chunk; cp2 < &dotp->l_text[dotp->l_used]; cp2++) *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) { 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; } } n -= chunk; } if (kchunk(sv, (RSIZE)len, kflag) != TRUE) goto out; rval = TRUE; out: free(sv); return (rval); } /* * Delete a newline and 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, this makes the kill buffer work * "right". If the mark is past the dot (actually, markline > dotline), * decrease the markline accordingly to keep line numbers in sync. * 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. We do not update w_dotline here, as deletes are done * after moves. */ int ldelnewline(void) { struct line *lp1, *lp2, *lp3; struct mgwin *wp; int s; if ((s = checkdirty(curbp)) != TRUE) return (s); if (curbp->b_flag & BFREADONLY) { dobeep(); ewprintf("Buffer is read only"); return (FALSE); } lp1 = curwp->w_dotp; lp2 = lp1->l_fp; /* at the end of the buffer */ if (lp2 == curbp->b_headp) return (TRUE); /* Keep line counts in sync */ curwp->w_bufp->b_lines--; if (curwp->w_markline > curwp->w_dotline) curwp->w_markline--; 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(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(lp1); free(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). */ int lreplace(RSIZE plen, char *st) { RSIZE rlen; /* replacement length */ struct line *lp; RSIZE n; int s, doto, is_query_capitalised = 0, is_query_allcaps = 0; int is_replace_alllower = 0; char *repl = NULL; if ((s = checkdirty(curbp)) != TRUE) return (s); if (curbp->b_flag & BFREADONLY) { dobeep(); ewprintf("Buffer is read only"); return (FALSE); } if ((repl = strdup(st)) == NULL) { dobeep(); ewprintf("out of memory"); return (FALSE); } rlen = strlen(repl); undo_boundary_enable(FFRAND, 0); (void)backchar(FFARG | FFRAND, (int)plen); if (casereplace != TRUE) goto done; lp = curwp->w_dotp; doto = curwp->w_doto; n = plen; is_query_capitalised = isupper((unsigned char)lgetc(lp, doto)); if (is_query_capitalised) { for (n = 0, is_query_allcaps = 1; n < plen && is_query_allcaps; n++) { is_query_allcaps = !isalpha((unsigned char)lgetc(lp, doto)) || isupper((unsigned char)lgetc(lp, doto)); doto++; if (doto == llength(lp)) { doto = 0; lp = lforw(lp); n++; /* \n is implicit in the buffer */ } } } for (n = 0, is_replace_alllower = 1; n < rlen && is_replace_alllower; n++) is_replace_alllower = !isupper((unsigned char)repl[n]); if (is_replace_alllower) { if (is_query_allcaps) { for (n = 0; n < rlen; n++) repl[n] = toupper((unsigned char)repl[n]); } else if (is_query_capitalised) { repl[0] = toupper((unsigned char)repl[0]); } } done: (void)ldelete(plen, KNONE); region_put_data(repl, rlen); lchange(WFFULL); undo_boundary_enable(FFRAND, 1); free(repl); return (TRUE); } /* * Allocate and return the supplied line as a C string */ char * linetostr(const struct line *ln) { int len; char *line; len = llength(ln); if (len == INT_MAX) /* (len + 1) overflow */ return (NULL); if ((line = malloc(len + 1)) == NULL) return (NULL); (void)memcpy(line, ltext(ln), len); line[len] = '\0'; return (line); }