Changeset View
Changeset View
Standalone View
Standalone View
contrib/mg/line.c
- This file was added.
| /* $OpenBSD: line.c,v 1.63 2021/03/01 10:51:14 lum 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 <sys/queue.h> | |||||
| #include <ctype.h> | |||||
| #include <limits.h> | |||||
| #include <signal.h> | |||||
| #include <stdio.h> | |||||
| #include <stdlib.h> | |||||
| #include <string.h> | |||||
| #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, curbp->b_nlchr, 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; | |||||
| if (ltext(lp) == NULL) | |||||
| goto done; | |||||
| 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); | |||||
| } | |||||