static char rcsid[] = "$Id: line.c,v 1.1 1992/09/05 01:13:32 mike Exp $"; /* $Log: line.c,v $ * Revision 1.1 1992/09/05 01:13:32 mike * Initial revision * */ /* * LINE.C * * The functions in this file are a general set of line management utilities. * They also touch the buffer and window structures, to make sure that the * necessary updating gets done. */ /* Copyright 1990, 1991, 1992 Craig Durland * Distributed under the terms of the GNU General Public License. * Distributed "as is", without warranties of any kind, but comments, * suggestions and bug reports are welcome. */ #include "me2.h" #define NBLOCK 16 /* Line block chunk size */ /* High Water mark: when allocating, always leave at least this much * free space */ #define HW 5 extern Mark *znext_mark(); extern Window *window_on_buffer(); static int ljoin(); static void fixmarks(), hole(), insert_c(); /* slide right part of line left, ie remove n chars @dot */ #define SLIDE(line,dot,n) \ blkmov(line->l_text +dot, line->l_text +dot +n, llength(line) -dot -n) /* * This routine allocates a block of memory large enough to hold a Line * containing used characters. The block is rounded up a bit if pad is * TRUE. * Return a pointer to the new Line, or NULL if there isn't any memory * left. * Print a message in the message line if not enough memory. */ Line *lalloc(used,pad) int used, pad; { register Line *lp; register int size = used; if (pad) { size = (used +HW +NBLOCK)/NBLOCK; size *= NBLOCK; } /* Remember that one char is in Line struct already! */ if ((lp = (Line *)malloc(sizeof(Line) -1 +size)) == NULL) { mlwrite("Cannot allocate %d bytes",size); return NULL; } lp->l_size = size; lp->l_used = used; return lp; } /* Link new_line before lp2. lp2 is already linked in. * Before: * lp0 <-> lp2 <-> lp3 * After: * lp0 <-> new_line <-> lp2 <-> lp3 */ static void llink_before(new_line,lp2) register Line *new_line, *lp2; { lp2->l_prev->l_next = new_line; /* lp0 -> new_line */ new_line->l_next = lp2; /* new_line -> lp2 */ new_line->l_prev = lp2->l_prev; /* lp0 <- new_line */ lp2->l_prev = new_line; /* new_line <- lp2 */ } #if 0 /* Link new_line over old_line. * Before: * lp1 <-> old_line <-> lp2 * After: * lp1 <-> new_line <-> lp2 */ static void llink_over(new_line, old_line) register Line *new_line, *old_line; { old_line->l_prev->l_next = new_line; /* lp1 -> new_line */ new_line->l_next = old_line->l_next; /* new_line -> lp2 */ new_line->l_prev = old_line->l_prev; /* lp1 <- new_line */ old_line->l_next->l_prev = new_line; /* new_line <- lp2 */ } #endif /* * This routine gets called when more than one line in current buffer is * changed. It updates all of the required flags in the buffer and window * system. The flag used is passed as an argument (it is almost always * WFHARD). * Set MODE if the mode line needs to be updated. */ void lchange(flag) register int flag; { if (!(curbp->b_flags & BFCHG)) /* First change, so */ { flag |= WFMODE; /* update mode lines */ curbp->b_flags |= BFCHG; } fixWB(curbp,flag); /* update all windows showing the current buffer */ } /* * This routine gets called when a line in the current buffer is edited. If * windows displaying the current buffer have had more than one line * edited, we need to do a HARD update. * lp is the line in the current buffer that has been edited. * Notes: * If lp get freed, make sure that the window flags are set to HARD so it * will be updated OK. If not, we won't be able to find the changed * line. * You would think that I could blow off seting the window flags if the * window was WFHARD. But there is a problem in that different windows * showing the same buffer may or may not be WFHARD - so you have to * look at each window anyway so you might as well do all the work. */ static void elchange(lp) Line *lp; { int flag = WFEDIT; register Window *wp; if (wp = window_on_buffer(curbp)) { if ((wp->w_flag & WFEDIT) && lp != wp->elp) { lchange(WFHARD); return; } } else return; /* no window displaying curbp => no updating needed */ if (!(curbp->b_flags & BFCHG)) /* First change, so */ { flag |= WFMODE; /* update mode lines */ curbp->b_flags |= BFCHG; } for ( ; wp; wp = wp->nextw) if (wp->wbuffer == curbp) { wp->w_flag |= flag; wp->elp = lp; } } /* * Insert n copies of a character 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. * Dot: The current dot is left after the inserted characters. * Dots: Don't move - they stay before the inserted characters. This means * you move them if they were after the current dot. * Marks: Same as dots. If a mark was set at dot and text inserted, we want * the dot to be after the new next and the mark to be before. This is * convenient. Trust me. * Return TRUE if everything went as expected, FALSE if can't alloc. */ linsert(n,c,overstrike) int n, overstrike; unsigned char c; { Line *old_line, *new_line; int doto, n1, i; save_for_undo(BU_INSERT,(int32)n); old_line = the_dot->line; /* Current line */ /* At the end of buffer: special */ if (old_line == BUFFER_LAST_LINE(curbp)) { if ((new_line = lalloc(n,TRUE)) == NULL) return FALSE; /* link in new_line before header_line (old_line) */ llink_before(new_line,old_line); insert_c(old_line,new_line, 0, n,c); return TRUE; } doto = the_dot->offset; n1 = n; /* Save for later */ if (overstrike) { i = doto +n; if (i <= llength(old_line)) goto stuff; if (i <= old_line->l_size) { old_line->l_used = i; goto stuff; } n -= llength(old_line) -doto; } /* Hard: reallocate */ if (old_line->l_used+n > old_line->l_size) { if ((new_line = lalloc(old_line->l_used +n,TRUE)) == NULL) return FALSE; blkmov(new_line->l_text,old_line->l_text,doto); /* leave hole for new text */ blkmov(new_line->l_text +doto +n, old_line->l_text +doto, old_line->l_used -doto); /* link in new_line to replace old_line */ old_line->l_prev->l_next = new_line; new_line->l_next = old_line->l_next; old_line->l_next->l_prev = new_line; new_line->l_prev = old_line->l_prev; free((char *)old_line); insert_c(old_line,new_line, doto, n1, c); return TRUE; } /* Easy: in place */ hole(old_line,doto,n); old_line->l_used += n; stuff: insert_c(old_line,old_line, doto, n,c); return TRUE; } /* insert_c: a helper routine for linsert() - inserts n characters at * doto in new_line and then updates all the marks pointing to * old_line. */ static void insert_c(old_line,new_line,doto,n,c) register Line *old_line, *new_line; int doto, n; unsigned char c; { elchange(new_line); { register unsigned char *ptr; register int i; ptr = &new_line->l_text[doto]; i = n; while (i--) *ptr++ = c; } /* Update windows */ { register Window *wp; for (wp = first_window; wp; wp = wp->nextw) { if (wp->top_line == old_line) wp->top_line = new_line; if (wp->dot.line == old_line) { wp->dot.line = new_line; if (wp->dot.offset > doto) wp->dot.offset += n; } } } /* Update marks */ { register Mark *mark; zsetup_marks(); /* Update the current dot */ mark = znext_mark(); mark->line = new_line; mark->offset += n; while (mark = znext_mark()) if (mark->line == old_line) { mark->line = new_line; if (mark->offset > doto) mark->offset += n; } } } /* * Insert a newline into the buffer at the current dot. 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. * Dot/Marks should update same as linsert(). * Dot: is left after the newline, ie at the start of the next line. * Dots: Same as marks. * Marks: Stay put if they were before or at dot. This makes the case * where you set mark at dot and insert a block of text work "right" - the * mark is at the start of the block and the dot is at the end. Its the * same behavior as inserting a character. * Return TRUE if everything works and FALSE on error (memory allocation * failure). */ lnewline() { register Line *dotp, *lp; register int doto; save_for_undo(BU_INSERT,(int32)1); lchange(WFHARD); /* more than one line is going to change */ dotp = the_dot->line; doto = the_dot->offset; if ((lp = lalloc(doto,FALSE)) == NULL) /* New first half line */ return FALSE; if (0 != doto) /* only shuffle text around if have to */ { blkmov(lp->l_text, dotp->l_text, doto); SLIDE(dotp,0,doto); } dotp->l_used -= doto; llink_before(lp,dotp); /* link in lp before dotp */ { /* Update windows */ register Window *wp; for (wp = first_window; wp; wp = wp->nextw) { if (wp->top_line == dotp) wp->top_line = lp; if (wp->dot.line == dotp) { if (wp->dot.offset <= doto) wp->dot.line = lp; else wp->dot.offset -= doto; } } } { /* Update marks */ register Mark *mark; zsetup_marks(); mark = znext_mark(); mark->offset = 0; /* Update the current dot */ while (mark = znext_mark()) if (mark->line == dotp) { if (mark->offset <= doto) mark->line = lp; else mark->offset -= doto; } } return TRUE; } /* Adjust dots and marks when part of a line is deleted. * 3 cases (Dot at 1, 2 or 3): * <---n----> * lp -> -------1----|------2---|----3----- * offset post * n characters deleted starting at offset. * If case 1: Don't need to do anything. * 2: Move dot back to offset. * 3: Move dot left n characters. */ static void futz1(lp,offset,post,n) register Line *lp; { register Mark *mark; register Window *wp; for (wp = first_window; wp; wp = wp->nextw) if (wp->dot.line == lp && offset < wp->dot.offset) if (post <= wp->dot.offset) wp->dot.offset -= n; /* 3 */ else wp->dot.offset = offset; /* 2 */ for (zsetup_marks(); mark = znext_mark(); ) if (mark->line == lp && offset < mark->offset) if (post <= mark->offset) mark->offset -= n; /* 3 */ else mark->offset = offset; /* 2 */ } /* Adjust dots and marks in the case where so much text has been deleted * after (lp,offset) that it has caused line lx to be deleted. If a dot * was on lx, it needs to be moved back to (lp,offset). */ static void futz2(lp,lx,offset) register Line *lp, *lx; { register Mark *mark; register Window *wp; for (wp = first_window; wp; wp = wp->nextw) { if (wp->top_line == lp) wp->top_line = lx; if (wp->dot.line == lp) { wp->dot.line = lx; wp->dot.offset = offset; } } for (zsetup_marks(); mark = znext_mark(); ) if (mark->line == lp) { mark->line = lx; mark->offset = offset; } } /* * This function deletes n characters, starting at dot. It understands how * do deal with end of lines, etc. * Input * n : number of characters to delete. Note that this is a 32 bit int, * make sure you pass it correctly. * save_text: TRUE if the text should be put in the cut buffer. * Returns: * TRUE if all of the characters were deleted. * FALSE if there were problems (malloc failed). * Note that deleting characters at the end of the buffer is considered OK * even though nothing is actually deleted. * Dot: Doesn't move - stays at the start of the deletion. * Dots: Several cases: If before or at dot, act like dot. If caught in * the middle of a big deletion - move back to the start of the deletion. * If after the deleted area, just stay put (move left the size of the * deletion). * Marks: Same as dots. */ ldelete(n,save_text) int32 n; int save_text; { register Line *dotp, *lp, *next_line; register int doto, chunk, wflag = 0, s = FALSE, i,t,z; save_for_undo(BU_DELETE,n); /* ??? how about a copy to bag for cut buffer and not screw with it * below? */ dotp = lp = the_dot->line; doto = i = the_dot->offset; t = TRUE; while (TRUE) { if (lp == BUFFER_LAST_LINE(curbp)) /* Hit end of buffer */ { s = TRUE; break; } next_line = lforw(lp); chunk = z = llength(lp) -i; if (z > n) z = n; if (save_text && z && !bag_append(cut_buffer,&lp->l_text[i],z)) break; if (n <= chunk || t) /* --xxx--, xxx, --xxx, xxx--- */ { if (z) { wflag = WFEDIT; SLIDE(lp,i,z); lp->l_used -= z; n -= z; futz1(lp,i,i+z,z); } if (n <= 0) { s = TRUE; break; } i = 0; t = FALSE; } else /* delete line */ { futz2(lp,dotp,doto); n -= chunk; free((char *)lp); } lp = next_line; n--; if (save_text && !bag_append(cut_buffer,"\n",1)) break; } t = TRUE; if (dotp != lp) { t = ljoin(dotp,lp); lchange(WFHARD); } else if (wflag) elchange(lp); return (s && t); } /* * Join two lines in the current buffer. * If line1 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 cut * buffer work "right". * If the next line will fit in the current line then shuffle data around. * else have to allocate a new line that can hold both. * Return FALSE on error and TRUE if all looks ok. * Dot: Doesn't move visually. * Dots: Same as dot. * Marks: Same as dot. */ static int ljoin(lp1,lp2) Line *lp1, *lp2; { int n; register Line *lp3; n = lp1->l_used; if (lp2 == BUFFER_LAST_LINE(curbp)) /* At the buffer end */ { if (n == 0) /* fold blank line into buffer end */ { futz2(lp1,lp2,0); lp1->l_prev->l_next = lp2; lp2->l_prev = lp1->l_prev; free((char *)lp1); return TRUE; } n = TRUE; xx: lp1->l_next = lp2; lp2->l_prev = lp1; return n; } /* if lp2 will fit at end of lp1, pack 'em */ if (lp2->l_used <= lp1->l_size -n) { blkmov(&lp1->l_text[n],lp2->l_text,lp2->l_used); lp1->l_used += lp2->l_used; lp1->l_next = lp2->l_next; lp2->l_next->l_prev = lp1; lp3 = lp1; lp1 = NULL; } else /* no fit, alloc a line big enough to hold both */ { if ((lp3 = lalloc(n +lp2->l_used, TRUE)) == NULL) { n = FALSE; goto xx; } blkmov(lp3->l_text, lp1->l_text, n); blkmov(lp3->l_text +n, lp2->l_text, lp2->l_used); /* Replace lp1 and lp2 with lp3 */ lp1->l_prev->l_next = lp3; lp3->l_next = lp2->l_next; lp2->l_next->l_prev = lp3; lp3->l_prev = lp1->l_prev; free((char *)lp1); } free((char *)lp2); { register Window *wp; for (wp = first_window; wp; wp = wp->nextw) { if (wp->top_line == lp1 || wp->top_line == lp2) wp->top_line = lp3; if (wp->dot.line == lp2) { wp->dot.line = lp3; wp->dot.offset += n; } else if (wp->dot.line == lp1) wp->dot.line = lp3; } } if (lp1) fixmarks(lp1,lp3,0); fixmarks(lp2,lp3,n); return TRUE; } /* open a hole of size n at dot in a line */ static void hole(line,dot,n) Line *line; int dot, n; { register unsigned char *ptr, *qtr; register int x; qtr = &line->l_text[llength(line) -1]; ptr = qtr +n; x = llength(line) -dot; while (x--) *ptr-- = *qtr--; } static void fixmarks(lp,lp1,n) Line *lp, *lp1; int n; { register Mark *mark; for (zsetup_marks(); mark = znext_mark(); ) if (mark->line == lp) { mark->line = lp1; mark->offset += n; } } /* This routine takes a line of text and a buffer pointer and appends the * text to the end of the buffer. This also effectively adds a newline to * the end of the text (ie text is appended as a line). * WARNINGS: * Don't pass this a \n! Use "" instead if you want a blank line. * Window flags are not updated - the caller has to take care of that. * You better know what you are doing. * Returns: TRUE is everything went as expected, FALSE if could not * allocate a line. */ buffer_append(bp,text) Buffer *bp; char *text; { int ntext; register Line *lp; ntext = strlen(text); if ((lp = lalloc(ntext,FALSE)) == NULL) return FALSE; blkmov(lp->l_text,text,ntext); llink_before(lp,BUFFER_LAST_LINE(bp)); return TRUE; }