static char rcsid[] = "$Id: buffer.c,v 1.2 1992/10/28 00:22:44 mike Exp $"; /* $Log: buffer.c,v $ * Revision 1.2 1992/10/28 00:22:44 mike * - Added support for the buffers window. * * Revision 1.1 1992/09/05 01:13:32 mike * Initial revision * */ /* * buffer.c : Buffer management. * Some of the functions are internal, and some are actually attached to * user keys. Like everyone else, they set hints for the display system. * * What is a buffer: * - Where all editing takes place. * Notes: * - Each buffer has a unique id. This id is for Mutt programming. */ /* 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 MAX_BUFFERS 16384 /* basically the max buffer id */ extern Buffer *alloc_buffer(); extern char *malloc(); extern Window *window_on_buffer(); extern void clear_buffer(), free_buffer(), use_buffer(); Buffer *curbp; /* Current buffer */ int bc_hook; /* buffer created hook */ /* Update the window flags of all windows displaying buffer */ void fixWB(bp,flag) Buffer *bp; { register Window *wp; for (wp = first_window; wp; wp = wp->nextw) if (wp->wbuffer == bp) wp->w_flag |= flag; } /* Return a pointer to nth buffer starting from bp, skipping the * buffers with the skip bit(s) set (usually the temp buffers). * Returns bp if bp is the only buffer with the skip bit(s) reset. */ Buffer *nthbuffer(bp,n,skip) Buffer *bp; { register Buffer *bx = bp; register int x = n; for (; 0 < x; x--) { if ((bx = bx->nextb) == NULL) bx = first_buffer; if (bx->b_flags & skip) /* skip temp buffers */ { if (bp == bx && x == n) break; /* get out of infinite loop */ x++; } } return bx; } #if 0 else /* count backwards */ { int cb, buffers; buffers = cntbufs((Buffer *)NULL); cb = cntbufs(bp,skip); if (buffers == 0) return bx; if ((n = (cb -(-n) % buffers)) < 0) n += buffers; /* ??? is n always >=0??? */ bx = nthbuffer(first_buffer,n,skip); } #endif /* return number of buffer bx. Use NULL to get number of buffers */ cntbufs(bx) Buffer *bx; { Buffer *bp = first_buffer; int j = 0; do { if (bp == bx) return j; j++; } while ((bp = bp->nextb) != NULL); return j; #if 0 register Buffer *bp; register int n = 0; for (bp = first_buffer; bp; bp = bp->nextb) if (!(bp->b_flags & skip_flags)) n++; return n; #endif } /* set_buffer_flags: Set flags for buffer bp and do things based on * those bits. This should be the only routine that sets buffer bits * but if you know what you are doing, you can fiddle them yourself. * Bits that trigger actions: * BFMODE: A tickle bit. Never set. If try to set this bit, force * all the modelines (showing buffer bp) to be updated. Ignore the * other bits. This is used by Mutt programs so they easily can get * (modeline-hook) called: (buffer-flags n BFMode). * BFCHG: If the change bit changes state, update all modelines * (showing buffer bp). If reset, make a note of that for undo. * BFUNDO: If the undo bit changes, turn undo on or off based on that * bit. * Other bit have no special actions. * Notes: * If a Mutt program resets the changed bits for all buffers, only the * current buffer will have that fact reflected in the undo list. * Input: * bp: Buffer to set flags. * flags: New flags. * Returns: Zip */ #define BIT_CHANGED(bit) (old_flags & (bit)) != (flags & (bit)) void set_buffer_flags(bp,flags) register Buffer *bp; int32 flags; { int32 old_flags; if (flags & BFMODE) /* tickle bit to force modeline-hook */ { fixWB(bp,WFMODE); return; } old_flags = bp->b_flags; if (BIT_CHANGED(BFCHG)) /* the modified bit */ { fixWB(bp,WFMODE); if (!(flags & BFCHG) && bp == curbp) /* current buffer unchanged */ save_for_undo(BU_UNCHANGED,(int32)0); } if (BIT_CHANGED(BFUNDO)) /* the undo bit */ { if (flags & BFUNDO) alloc_buffer_undos(bp); /* turn on undo */ else free_buffer_undos(bp); /* turn off undo */ /* Let undo code set bits: lots could happen when change undo state */ /* Reset flags to reflect true state of undo bit */ flags = (flags & ~BFUNDO) | (bp->b_flags & BFUNDO); } bp->b_flags = flags; } #if 0 #define BP_SET_BITS 0 #define BP_TURN_ON_BITS 1 #define BP_TURN_OFF_BITS 2 #define BP_ARE_BITS_SET 3 int diddle_buffer_flags(bp,bits,op) register Buffer *bp; int32 bits; { int32 flags = bp->b_flags; switch (op) { case BP_SET_BITS: flags = bits; break; case BP_TURN_ON_BITS: flags |= bits; break; case BP_TURN_OFF_BITS: flags ~= !bits; break; case BP_ARE_BITS_SET: return (flags & bits) != 0; } set_buffer_flags(bp,flags); return TRUE; } #endif /* Check to see a buffer has been marked as changed. * Input: * bp : buffer to check. * Returns: * !0: bp has been modified. * 0: bp has not been modified. */ is_buffer_modified(bp) register Buffer *bp; { return (bp->b_flags & BFCHG); } /* Set the modified bit for a buffer. * Input: * bp : buffer to check. * modified: TRUE: set the modified bit to modified. */ void set_buffer_modified(bp,modified) register Buffer *bp; { int32 flags = bp->b_flags; if (modified) flags |= BFCHG; else flags &= ~BFCHG; set_buffer_flags(bp,flags); } /* (switch-to-buffer) * Select a new current buffer and attach it to a current window. * If f then the buffer must exist. * Changes the current buffer and current window. * Bound to "C-x b" */ switch_to_buffer(f,n) register int f; int n; { register int s; char bufn[NBUFN]; if ((s = mlreply("Use buffer: ",bufn,NBUFN,CC_BUF)) != TRUE) return s; if ((s = useb(bufn,!f)) == FALSE && f) mlwrite("Buffer \"%s\" does not exist.",bufn); return s; } /* * Attach a buffer to the current window (detaching the current buffer). * If cflag, create buffer if it don't exist. * This is really a window routine. */ useb(buf_name,cflag) char *buf_name; { register Buffer *bp; if (!(bp = bfind(buf_name,cflag,BFINTERACTIVE))) return FALSE; use_buffer(bp,TRUE); return TRUE; } /* Pop up a window and attach a buffer to it. Make sure at least 1 * window is displayed, splitting the screen if this is what it takes. * Lastly, repaint all of the windows that are displaying the buffer. * If makecurrent, dot is sent to the start of the buffer. * Returns: TRUE if everything went as expected, FALSE if couldn't get a * window. */ bpopup(bptr,makecurrent) Buffer *bptr; int makecurrent; { extern Window *wpopup(); /* Pop up window creation */ register Window *wp; if (!window_on_buffer(bptr)) /* buffer is not being displayed */ { if ((wp = wpopup()) == NULL) return FALSE; wp->wbuffer = bptr; } /* else don't know what windows attached to buffer already */ fixwin(bptr,makecurrent); return TRUE; } /* Check to see if a buffer is munged (ie we care about the contents of * a buffer and they have not been saved yet). */ #define BUFFER_MUNGED(bp) (((bp)->b_flags & (BFNOCARE | BFCHG)) == BFCHG) /* * Look through the list of buffers to see if there are any munged buffers. * Returns: * TRUE if there are any changed buffers that we care about. * FALSE if no buffers have been changed. */ anycb() { register Buffer *bp; for (bp = first_buffer; bp; bp = bp->nextb) if (BUFFER_MUNGED(bp)) return TRUE; return FALSE; } /* * Find a buffer, by name. Return a pointer to the Buffer structure * associated with it. * If the buffer is not found and create is TRUE, create a new buffer with * buffer flags bflag. */ Buffer *bfind(bname, create, bflag) register char *bname; int create, bflag; { register Buffer *bp; for (bp = first_buffer; bp; bp = bp->nextb) if (strcmp(bname,get_dString(bp->b_bname)) == 0) return bp; if (create) return alloc_buffer(bname, (int32)(bflag | BFIMMORTAL)); return NULL; } /* * This routine blows away all of the text in a buffer. * If the buffer is marked as changed then we ask if it is OK to blow it * away; this is to save the user the grief of losing text. The window * chain is nearly always wrong if this gets called; the caller must * arrange for the updates that are required. * Returns: * TRUE if everything looks good. * FALSE or ABORT otherwise. */ bclear(bp) register Buffer *bp; { int s; if (BUFFER_MUNGED(bp) && (s = mlyesno("Discard changes")) != TRUE) return s; clear_buffer(bp); return TRUE; } /* ******************************************************************** */ /* *********** Hooks and things *************************************** */ /* ******************************************************************** */ /* Set the current buffer to bp and call hook. * Note: I'm not sure about the possible consequences of this but there * could be some nasties if certain things are done while in this state. * Returns: TRUE: hook exists and is called, FALSE if hook doesn't exist. */ bhook(bp,hook) Buffer *bp; { Buffer *bp1; if (hook == -1) return FALSE; bp1 = curbp; use_buffer(bp,FALSE); MMrun_pgm(hook); use_buffer(bp1,FALSE); return TRUE; } /* ******************************************************************** */ /* ********** Buffer implementation details *************************** */ /* ******************************************************************** */ /* Initialize the buffer system. * This can be called most anytime during startup - but before the window * system is inited and after the mark system is inited. * Might want to cache some buffers since its very likely that more than * one will be used and having a few already allocated at the front of * the heap might help malloc() avoid thrashing. */ buf_init(bname) char *bname; { Buffer *bp; if (!init_bvars()) return FALSE; /* allocate the new current buffer */ if (!(bp = alloc_buffer(bname,(int32)BFIMMORTAL))) return FALSE; use_buffer(bp,FALSE); return TRUE; } Buffer *first_buffer = NULL; static Buffer *freed_buffers = NULL; /* Given a buffer id, return a pointer to the buffer that has that id */ Buffer *id_to_buffer(buffer_id) int buffer_id; { register Buffer *buffer; for (buffer = first_buffer; buffer; buffer = buffer->nextb) if (buffer->id == buffer_id) return buffer; return NULL; } /* * Delete all of the text saved in a buffer. * The window chain is nearly always wrong if this gets called - make sure * all windows showing bp get updated. * header_line is kept as part of the buffer, don't delete it. * Notes: * Undo is a stinker here. I think this and file reads are the only places * where I change a buffer that is not the current one. I also don't * know how many bytes are going away, don't want to spend to check and * save_for_undo() doesn't like big numbers. My sleeze-around is to * clear out undos if I clear any text in the buffer. */ void clear_buffer(bp) register Buffer *bp; { int freed_lines; register Line *header_line, *lp, *ln; header_line = BUFFER_HEADER_LINE(bp); /* free all text in buffer */ ln = lforw(header_line); /* first line of buffer text */ freed_lines = (ln != header_line); while (ln != header_line) { lp = ln; ln = lforw(ln); free((char *)lp); } /* relink header line */ header_line->l_next = header_line->l_prev = header_line; clear_buffer_marks(bp); /* invalidate marks */ fixwin(bp,FALSE); /* fix window flags */ if (freed_lines) { set_buffer_modified(bp,TRUE); /* Big time buffer change */ clear_undos(bp); /* ick */ } } /* Allocate a buffer and return a pointer to it. * If you need the buffer id, its in the buffer. * Look for a free buffer before expanding the table. * Call buffer-created-hook for the newly created buffer. * WARNING * Be careful if you call this during startup: I call * set_buffer_flags() (so the flags are set for buffer-created-hook) * and that can diddle the window system. So don't call with flags * that do much. Actually, I think its pretty bomb proof but that * could change in the future. * Input: * bname : What to name the new buffer. * flags : Buffer bits * Returns: * NULL : out of memory or I already allocated all the buffers I going * to allococate. * Pointer to the new buffer. */ Buffer *alloc_buffer(bname,flags) char *bname; int32 flags; { static int num_buffers = 0; register Buffer *bp, *bp1, *bp2; register Line *lp; if (freed_buffers) /* there are buffers in the free pool */ { bp = freed_buffers; freed_buffers = freed_buffers->nextb; } else /* malloc a buffer */ { /* don't alloc more than MAX_BUFFERS! */ if (num_buffers >= MAX_BUFFERS) return NULL; /* ??? create extras??? */ /* no buffers in the pool, create one */ if (!(bp = (Buffer *)malloc(sizeof(Buffer)))) return NULL; bp->id = num_buffers; num_buffers++; } /* keep buffer list sorted by name */ for (bp1 = bp2 = first_buffer; bp2; bp1 = bp2, bp2 = bp2->nextb) if (strcmp(get_dString(bp2->b_bname),bname) >= 0) break; /* insert bp into the buffer list between bp1 and bp2 */ if (bp2 == first_buffer) first_buffer = bp; /* goes at front of list */ else bp1->nextb = bp; /* end or middle of list */ bp->nextb = bp2; lp = BUFFER_HEADER_LINE(bp); lp->l_size = lp->l_used = 0; lp->l_next = lp->l_prev = lp; /* initialize all the data in the buffer */ init_buffer_marks(bp); /* !!! I should check for errors (and do what?) */ init_buffer_keys(bp); init_dString(&bp->b_bname); set_dString(&bp->b_bname,bname); init_dString(&bp->b_fname); bp->wrap_col = bp->tabsize = 0; init_buffer_bvars(bp); init_buffer_undos(bp); bp->b_flags = 0; set_buffer_flags(bp,flags); bhook(bp,bc_hook); /* call buffer-created-hook */ #ifdef atarist redraw_buffers_window(); #endif return bp; } /* Free a buffer and its contents and return it to the buffer pool. * If the buffer is being displayed, undisplay it. * Cases (in order): * 1) Free the last buffer. Create a scratch buffer and then free the * buffer. ME always expects at least one buffer around. If freeing * the scratch buffer, create a new and go ahead and free the old * one. This way buffer-created-hook is called as expected. Note * that the last buffer has to be the current buffer (otherwise there * would be no current buffer) and it will be displayed. * 2) If the current buffer is being deleted, find a new one. MUST * always have a good current buffer. If all other buffers are * hidden, pretend that this is the last buffer and create *scratch*. * 3) Buffer not displayed - easy, just free the stuff it uses and * remove it from active duty. * 4) Buffer displayed and it is the current buffer: Find a new current * buffer (as in 2) and display it in all windows showing buffer. * Then do 2. * 5) Buffer displayed and it's not current buffer: Go though the * window list and have all the windows displaying the * buffer-to-be-deleted display the current buffer. * Drawbacks: * May look a little funny if the current buffer is hidden. * Behaves different than 4 which will create *scratch* if other * buffers are hidden. * Notes: * If windows are messed with, remember to update the window flags. * Errors: * If I can't allocate a buffer, bp is just cleared. */ void free_buffer(bp) register Buffer *bp; { register Buffer *bp1; register Window *wp; if (curbp == bp) /* or last buffer */ { /* find a buffer to replace bp with. If none, create *scratch*. */ if ( (bp1 = nthbuffer(bp,1,BFHIDDEN)) == bp && !(bp1 = alloc_buffer(SCRATCH_BUFFER, (int32)(BFIMMORTAL | BFINTERACTIVE)))) return; use_buffer(bp1,FALSE); } for (wp = first_window; wp; wp = wp->nextw) if (wp->wbuffer == bp) display_buffer(wp); clear_buffer(bp); /* Blow text away */ free_dTable(&bp->lkeys); /* remove the local key bindings */ free_dString(&bp->b_fname); /* get rid of the file name */ free_dString(&bp->b_bname); free_buffer_undos(bp); /* free all the undo stuff */ free_buffer_bvars(bp); free_buffer_marks(bp); /* add buffer to free pool */ if (first_buffer == bp) first_buffer = bp->nextb; else { for (bp1 = first_buffer; bp1->nextb != bp; bp1 = bp1->nextb) ; bp1->nextb = bp->nextb; } bp->nextb = freed_buffers; freed_buffers = bp; #ifdef atarist redraw_buffers_window(); #endif } Mark *the_dot; /* pointer to the dot in the current buffer */ /* * Make bp the new current buffer. If display_it, also attach bp to the * current window (detaching the current buffer). */ void use_buffer(bp,display_it) Buffer *bp; { curbp = bp; the_dot = id_to_mark(THE_DOT); do_undo = bp->b_flags & BFUNDO; if (display_it) display_buffer(curwp); } /* Garbage collect all buffers. * This means freeing all buffers not marked immortal. * See the spec for MMgc_external_objects() for when and why this is * called. */ void gc_buffers() { register Buffer *bp, *bw; for (bp = first_buffer; bp; bp = bw) { bw = bp->nextb; /* since pointer is gone after free_buffer() */ if (!(bp->b_flags & BFIMMORTAL)) free_buffer(bp); } }