static char rcsid[] = "$Id: bag.c,v 1.1 1992/09/05 01:13:32 mike Exp $"; /* $Log: bag.c,v $ * Revision 1.1 1992/09/05 01:13:32 mike * Initial revision * */ /* * bag.c * Routines to handle bags of data - the basic things used to move chunks of * data around. * What is a bag: * - A bag holds text, as a character stream or in rectanglar form. * - A rectangle is N rows of M columns, each row having M characters (no * short lines). * - Bags are used to move text to/from buffers, files, etc. They are the * text transport mechanism. * Notes: * - Bags are never deleted. This makes it easy to ensure bad ids are * always unique. Other reasons follow. * - Want to be able to use pointers to bags (internally) for speed and * ease of use. This means no dTables because realloc() can invalidate * pointers. * - Since bags can be created/used/freed both by ME2 and Mutt pgms, I * don't want pgms to be able to free a bag and cause ME2 to core dump * because a bag pointer is invalid. * - Because bags can be created by Mutt programs and Mutt programmers can * be sloppy or the program terminate abnormally, I need to be able to * garbage collect bags. This is a limited GC because the only time I * know when a bag is garbage is when the "root" pgm has terminated and * the programmer has marked the bag as collectable. Oh well. Anyway, * I use a mortal or immortal bit for this. Mortal means collectable * and immortal means somebody has to explicitly free the bag to remove * it. I mix this bit into the bag type to save some space in the bag * structure. See the spec for MMgc_external_objects() for more info. */ /* 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" /* bag types */ #define BAG_OF_TEXT 0 #define BAG_OF_RECTANGLE 1 /* If the immortal bit is 0, bag can be recycled. */ #define IMMORTAL_BAG 0x80 #define BAG_TYPE(bag) ((bag)->type & 0x0F) #define MAX_BAGS 4096 #define RBLOCK 1024 /* bag growth block size */ extern char *malloc(); static int insert_rect(); /* ******************************************************************** */ /* ********** Bag implementation details ****************************** */ /* ******************************************************************** */ static Bag *first_bag = NULL, *freed_bags = NULL; /* Given a bag id, return a pointer to the bag that has that id */ Bag *id_to_bag(bag_id) int bag_id; { register Bag *bag; for (bag = first_bag; bag; bag = bag->next) if (bag->id == bag_id) return bag; return NULL; } bag_type(bag) Bag *bag; { return BAG_TYPE(bag); } void set_bag_type(bag, type) Bag *bag; { bag->type = (bag->type & IMMORTAL_BAG) | type; } /* * Delete all of the text saved in a bag. * To avoid thrashing, only free the bag if it is indeed big. This will * keep reusing the same buffer until it is necessary to allocate more mem * to the hold the text. */ void clear_bag(bag) register Bag *bag; { if (bag->text.max_items > RBLOCK) { free_dTable(&bag->text); INIT_dTable(&bag->text); } else reset_dTable(&bag->text); set_bag_type(bag, BAG_OF_TEXT); } /* Allocate a bag and return a pointer to it. * If you need the bag id, its in the bag. * Look for a free bag before expanding the table. */ Bag *alloc_bag(immortal) int immortal; { static int num_bags = 0; register Bag *bag; if (freed_bags) /* there are bags in the free pool */ { bag = freed_bags; freed_bags = freed_bags->next; } else /* malloc a bag */ { /* don't alloc more than MAX_BAGS! */ if (num_bags >= MAX_BAGS) return NULL; /* no bags in the pool, create one */ if (!(bag = (Bag *)malloc(sizeof(Bag)))) return NULL; INIT_dTable(&bag->text); bag->id = num_bags; num_bags++; } bag->next = first_bag; first_bag = bag; bag->type = BAG_OF_TEXT; if (immortal) bag->type |= IMMORTAL_BAG; return bag; } /* Free a bag and return it to the bag pool. * WARNING * If free a freed bag, core dump city. I'll let this slide because * ME2 never frees bags, only pgms and they have to check first. * Notes: * It is possible for a Mutt program to free the cut buffer. I think * everything would still work (no core dumps) but might act strange. */ void free_bag(bag) register Bag *bag; { register Bag *bbag; /* clear_bag(bag); */ free_dTable(&bag->text); INIT_dTable(&bag->text); bag->type = BAG_OF_TEXT; /* not immortal */ if (first_bag == bag) first_bag = bag->next; else { for (bbag = first_bag; bbag->next != bag; bbag = bbag->next) ; bbag->next = bag->next; } bag->next = freed_bags; freed_bags = bag; } /* Garbage collect all bags. * This means freeing all bags not marked immortal. * See the spec for MMgc_external_objects() for when and why this is * called. */ void gc_bags() { register Bag *bp, *bw; for (bp = first_bag; bp; bp = bw) { bw = bp->next; /* since pointer is gone after free_bag() */ if (!(bp->type & IMMORTAL_BAG)) free_bag(bp); } } /* * Append z characters to bag, enlarging the bag if there isn't enough room. * Always grow the bag in chunks, on the assumption that if you put * something in you are going to put more stuff there later. * WARNING!!! * On systems with 16bit ints (MS-DOS), having z be an int means a max bag * size of 32k. This is bogus but hard to change because dTables would * have to change. * Returns: * TRUE: Everything went as expected * FALSE: Bag overflow. */ bag_append(bag,text,z) Bag *bag; char *text; int z; { int j; j = sizeof_dTable(&bag->text); if (!xpand_dTable(&bag->text, z, RBLOCK, RBLOCK)) return FALSE; blkmov(&bag->text.table[j], text, z); return TRUE; } insert_bag(bag) Bag *bag; { if (BAG_TYPE(bag) == BAG_OF_TEXT) return insert_block(bag->text.table, (int32)sizeof_dTable(&bag->text)); return insert_rect(bag); } /* ******************************************************************** */ /* ****************** Rectangles ************************************** */ /* ******************************************************************** */ /* A rectangle is the rectanglar region outlined by a mark at the upper left * corner and a mark at the lower right corner. It is R rows by C * columns. Once the rectangle gets into a bag, each row has C characters * in it (ie the short lines are padded with white space). * For now, I use the dot and the mark to outline rectangles because thats * easy and I haven't yet seen a need to make these more general. Also, * insert_rect() works like insert_block(). Probably a short sighted * view. */ /* Copy a rectangle in the current buffer to a bag. * Marks are unmoved. * Input: * bag: Pointer to bag rectangle will be copied to. * mark1, mark2: pointer to marks that mark the upper left and lower * right corner of the rectangle. Order not important. * Returns: FALSE if not a region, else TRUE. * Notes: * !!! I don't check for out of memory! */ copy_rect(bag, mark1,mark2) Bag *bag; Mark *mark1, *mark2; { char c; int i, j, k, w, a = 0; Region region; Line *lp; if (get_rectangle(mark1,mark2, ®ion) == 0) return FALSE; clear_bag(bag); bag->width = region.width; bag->height = region.height; lp = region.mark.line; while (region.height--) { w = region.width; k = llength(lp); i = getgoal(lp,region.ulcol); j = getcol(lp,i); if (j != region.ulcol) /* in middle of a tab or at end of line */ a = region.ulcol -j; while (0 < w && i < k) { c = lgetc(lp,i); if (c == '\t') /* expand tabs */ { j = getcol(lp,i); j = NEXT_TAB_COLUMN(j) -j -a; a = 0; while (j-- && w--) bag_append(bag," ",1); } else { bag_append(bag,&c,1); w--; } i++; } while (0 < w--) bag_append(bag," ",1); /* take care of short line */ lp = lforw(lp); } set_bag_type(bag, BAG_OF_RECTANGLE); return TRUE; } /* Insert a rectanglar bag at dot. * WARNING! only call this with a bag that contains a rectangle! * Dot marks the upper left of the rectangle, dot is left there. * Mark is left at the lower right corner. * Notes: * Doesn't work like insert_block(). */ static int insert_rect(bag) Bag *bag; { char *text; int col, w, h; set_mark(THE_MARK); col = getccol(); w = bag->width; h = bag->height; text = bag->text.table; while (TRUE) { insert_text(text,w); if (--h == 0) break; text += w; next_line(1); the_dot->offset = getgoal(the_dot->line,col); to_col(col); /* in case of tab or end of line */ } swap_marks(THE_DOT,THE_MARK); return TRUE; } /* Delete or clear rectangle. * Dot is left at the upper left corner of where the rectangle used to be. * Mark is left at the lower left corner of where the rectangle used to be. * Returns: FALSE if no region, else TRUE. */ erase_rect(delete) { int j,cw,a,b,n,z,col,h; Region r; Line *lp; if (get_rectangle(id_to_mark(THE_DOT), id_to_mark(THE_MARK), &r) == 0) return FALSE; *the_dot = r.mark; /* put dot at upper left corner of rectangle */ set_mark(THE_MARK); cw = (col = r.ulcol) +r.width; h = r.height; while (TRUE) { z = TRUE; n = delete ? col : cw; lp = the_dot->line; the_dot->offset = a = getgoal(lp,col); b = getgoal(lp,cw); if (b == llength(lp)) z = FALSE; /* short line */ else /* tab or at column cw */ { j = getcol(lp,b); if (j < cw) /* tab */ if (delete) { j = getcol(lp,++b); n = j -r.width; } else n = j; } ldelete((int32)(b-a),FALSE); if (z) to_col(n); if (--h == 0) break; /* leave dot at lower right corner of rectangle */ next_line(1); } /* put dot at upper left corner of rectangle */ swap_marks(THE_DOT,THE_MARK); /* ??? next 2 lines needed??? */ the_dot->offset = getgoal(the_dot->line,col); to_col(col); return TRUE; } /* ******************************************************************** */ /* ************* Bag utilities **************************************** */ /* ******************************************************************** */ char *bag_text(bag) Bag *bag; { return bag->text.table; } int bag_size(bag) Bag *bag; { return sizeof_dTable(&bag->text); } /* Remove n character from the end of the bag */ void trim_bag(bag,n) Bag *bag; { sizeof_dTable(&bag->text) -= n; } /* Convert a bag to a string. * Gonna look a little funny if you convert a rectangle to a string. * NOTES * This just adds a '\0' to the end of the bag so you can use it like * a C string. I remove the '\0' but it will stick around until * overwritten ie the string is valid until the bag is written to. * WARNING * You better do something with the string before the bag is munged * again! Or you will have a very long string or a seg violation. * Input: * bag : pointer to a bag. * Returns: * NULL : Out of memory. * Pointer to a NULL terminated string. */ char *bag_to_string(bag) Bag *bag; { if (!bag_append(bag, "\0", 1)) return NULL; /* NULL terminate string */ trim_bag(bag,1); /* just kidding */ return bag_text(bag); } /* Slide the end of the bag towards the front by n characters */ void slide_bag(bag,n) Bag *bag; { char *text = bag->text.table; blkmov(text, text+n, bag_size(bag) -n); trim_bag(bag,n); } /* Insert a block of text (n characters long) at dot. * Dot is left after the inserted characters. * Input: * text: Pointer to characters to be inserted. Not a string - no \0 * expected. * n: Number of character of text to insert. * Returns: * TRUE: Everything when as expected. * FALSE: out of memory, ??? * In this case, the undo buffer may be messed up. * Notes: * I turn off the undo flag so that the various routines I call (like * openline(), ldelete()) won't mess the undo buffer. * Error checking is real weak. */ insert_block(text,n) char *text; int32 n; { int s, undo_flag; int32 i, j; unsigned int size; /* 16 bits OK here */ Line *lp1, *lp2, *anchorline; if (n == 0) return TRUE; save_for_undo(BU_INSERT,n); undo_flag = do_undo; do_undo = FALSE; s = TRUE; /* split the current line at the cursor */ if (!openline(FALSE,1)) /* leave dot on first line */ { s = FALSE; goto done; } /* undo may blotto'ed */ i = j = 0; lp2 = the_dot->line; /* line containing dot. Insert new line after this */ anchorline = lp2->l_next; /* Second half of split line. This never moves */ while (i < n) { while (j < n && text[j] != '\n') j++; size = j -i; if ((lp1 = lalloc((int)size,FALSE)) == NULL) { s = FALSE; break; } lp1->l_prev = lp2; lp2->l_next = lp1; /* link in new line */ blkmov((lp2 = lp1)->l_text, &text[i], size); i = ++j; } lp2->l_next = anchorline; anchorline->l_prev = lp2; /* make the last link */ ldelete((int32)1, FALSE); /* rejoin split line to inserted line */ the_dot->line = anchorline; the_dot->offset = 0; if (text[n-1] != '\n') delete_characters(-1, FALSE); done: do_undo = undo_flag; return s; } #include /* file_to_bag: Copy the contents of a file to a bag. * The file is appended to the end of the bag. * Returns: TRUE if everything worked, FALSE if problems. */ #define BSIZ 512 file_to_bag(file_name,bag) char *file_name; Bag *bag; { char buf[BSIZ]; FILE *fptr; int n; if ((fptr = fopen(file_name,"r")) == NULL) { mlwrite("Can't open %s",file_name); return FALSE; } while (n = fread(buf,1,BSIZ,fptr)) /* error checking ?!?*/ if (!bag_append(bag,buf,n)) return FALSE; fclose(fptr); return TRUE; } /* bag_to_file: Copy the contents of a bag to a file. * The file is overwritten, old contents lost. ????????send in a append flag? * Returns: TRUE if everything worked, FALSE if problems. */ bag_to_file(bag,file_name) Bag *bag; char *file_name; { FILE *fptr; int s; if ((fptr = fopen(file_name,"w")) == NULL) { mlwrite("Can't open \"%s\"",file_name); return FALSE; } s = fwrite(bag->text.table,1,sizeof_dTable(&bag->text),fptr); fclose(fptr); if (sizeof_dTable(&bag->text) > s) { unlink(file_name); /* clean up */ mlwrite("Not enough disk space!"); return FALSE; } return TRUE; }