static char rcsid[] = "$Id: mark.c,v 1.1 1992/09/05 01:13:32 mike Exp $"; /* $Log: mark.c,v $ * Revision 1.1 1992/09/05 01:13:32 mike * Initial revision * */ /* mark.c : Mess with buffer marks. * * This file contains all the routines to manipulate marks. If you know * what you are doing, you can change the contents of a mark but don't do * much else - let these routines do it for you. * Marks live in buffers and point to buffer contents. They can not refer * to anything outside of the buffer they live in. * A mark is alive if it has been allocated. * A mark is valid if it is alive and mark.line != NULL. * Marks are identified by their id. The id of each mark is unique within * its buffer. You can have pointers to marks because marks are never * free()ed (ie the pointer is always good) but the mark may become * invalid. You should always ask for a mark by id, then you can use the * pointer. This is the only way Mutt programs can get at marks. * Mark id 0 is the dot. * Mark id 1 is the mark. * Buffers always have marks 0 and 1 (dot and mark). The only way to delete * them is to free the buffer. Dot (mark 0) is always valid. * Marks 0 and 1 are never put on the free list and mark 0 is ALWAYS valid. * Marks point point between 2 characters. If a line contains "ab", * mark->offset == 0 means the mark if before the "a", 2 means between "a" * and "b" and 3 is after the "b". Any other value for offset is invalid. * * Mutt programming: * Mutt gets to allocate, free, set and goto mark(s) - pretty much the * whole nine yards. All mark ops refer to marks via mark ids. * * C Durland 10/90. Major mods 9/91 */ /* 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" /* * Set the mark in the current buffer to the value of dot in the current * buffer. * Returns: * TRUE: OK * FALSE: Invalid mark id. */ set_mark(mark_id) int mark_id; { Mark *mark; if (!(mark = id_to_mark(mark_id))) return FALSE; *mark = *the_dot; return TRUE; } /* * Set the dot to mark in the current buffer. * WARNING * mark must to be valid! If it ain't, things will get real strange. The * only way for this code to up screw is if mark->line is set to garbage * (as was the case when I forgot to initialize it when it was created). * Returns: * TRUE: OK * FALSE: Invalid mark id or mark has not been set. */ jmp_mark(mark) Mark *mark; { if (!mark->line) return FALSE; *the_dot = *mark; dot_moved(); return TRUE; } goto_mark(mark_id) int mark_id; { Mark *mark; if (!(mark = id_to_mark(mark_id))) return FALSE; return jmp_mark(mark); } /* Swap 2 marks. * WARNING * If swapping dot, the new dot must be valid - NOT null! We know that dot * is always good, so we only have to check mark. * Both marks must be in the same buffer. * Returns: * TRUE: OK * FALSE: Invalid mark id or mark has not been set (if changing dot). */ swap_marks(mrk1_id, mrk2_id) int mrk1_id, mrk2_id; { Mark *mark1, *mark2, tmp_mark; if (!(mark1 = id_to_mark(mrk1_id)) || !(mark2 = id_to_mark(mrk2_id))) return FALSE; if ((mrk1_id == THE_DOT || mrk2_id == THE_DOT) && (!mark1->line || !mark2->line)) return FALSE; #if 0 if (mrk1_id == THE_DOT || mrk2_id == THE_DOT) dot_moved(); #endif tmp_mark = *mark1; *mark1 = *mark2; *mark2 = tmp_mark; return TRUE; } /* * Set the mark in the current window to the value of dot in the window. No * errors are possible. * Bound to "M-." & "C-@". */ setmark(f,n) { set_mark(THE_MARK); mlwrite("Mark set."); return TRUE; } /* * Swap the values of dot and mark in the current window. This is pretty * easy, bacause all of the hard work gets done by the standard routine * that moves the mark about. The only possible error is "no mark". * Bound to "C-x C-x". */ swapmark(f,n) { if (!swap_marks(THE_DOT,THE_MARK)) { mlwrite("No mark"); return FALSE; } return TRUE; } /* ******************************************************************** */ /* ******************** Gory Details ********************************** */ /* ******************************************************************** */ /* Marks are kept in a big linked list. Marks that are not currently being * used are kept in a free pool on the assumption that this will reduce * malloc trashing since marks are used and reused often. An attempt is * made to further reduct heap fragmentation by malloc()ing lots of marks * when some are needed. */ /* Some code (the update the marks when lines change code) assumes that the * first mark is the dot. Since I don't make many promises about data * structures, you must use the routines provided to traverse the mark * list. */ /* Each and every mark has a unique id. Because it makes the code easier (I * can recycle marks without worrying about ids clashing). In each buffer * the first 2 marks are special cased: the first mark is the dot and * second is the mark (note that this implies that those two marks are * always in the buffer). Everybody (outside of this code) thinks that * the first mark in a buffer has an id of 0 (THE_DOT) and that the buffer * has a mark with id 1 (THE_MARK). Well, that ain't the case so * everybody needs to use id_to_mark() to the deception work. */ /* A pointer is kept to the dot in the current buffer (the_dot in buffer.c). * If you move the the dot, you better update the pointer! */ typedef struct MarkThing { Mark mark; /* free_buffer_mark() depends on this being first */ struct MarkThing *next; int16 id; uint8 flags; /* a waste of space! but needed */ } MarkThing; static MarkThing *freed_marks = NULL; static int id_seed = THE_MARK +1; /* No mark has id 0 or 1 */ static MarkThing *alloc_mark(); Mark *id_to_mark(mark_id) int mark_id; { register MarkThing *mark; mark = (MarkThing *)(curbp->marks); /* First mark in the buffer */ switch(mark_id) { case THE_DOT: return &mark->mark; /* mark id 0 is the first mark */ case THE_MARK: return &mark->next->mark; /* mark id 1 is the second mark */ } /* Skip to first 2 marks to speed things up */ for (mark = mark->next->next; mark; mark = mark->next) if (mark->id == mark_id) return &mark->mark; return NULL; } /* Invalidate all marks except dot. * Dot is set to the first line of the buffer. * This is (usually) called when the buffer is being cleared. */ void clear_buffer_marks(bp) Buffer *bp; { register MarkThing *mark; /* Set the dot */ mark = (MarkThing *)(bp->marks); mark->mark.line = BUFFER_FIRST_LINE(bp); mark->mark.offset = 0; /* Invalidate the rest of the marks */ while (mark = mark->next) mark->mark.line = NULL; } /* Initialize the marks in a buffer. This must be called when a buffer * is created. * Input: * bp : pointer to the buffer that has just been created. * Returns: * TRUE: Everything OK * FALSE: Probably out of memory but maybe there are too many marks. */ int init_buffer_marks(bp) Buffer *bp; { MarkThing *dot, *mark; if (!(dot = alloc_mark(TRUE)) || !(mark = alloc_mark(TRUE))) return FALSE; bp->marks = (Mark *)dot; dot->next = mark; mark->next = NULL; clear_buffer_marks(bp); return TRUE; } #define MNX 80 /* Number of marks to allocate when I malloc() */ #define MAX_MARKS 20000 /* A max (< 32K) to keep ids from wrapping around */ /* Allocate a mark. * Returns: * Pointer to the allocated mark. * NULL if can't malloc(). * Notes: * Only the following are set: * mark.line set to NULL to invalidate the mark * id. * immortal flag. * You need to set the rest (like link in the mark). * Input: * immortal : TRUE if the created mark can't be garbage collected. * Returns: * Pointer to the created mark. * NULL if no memory or created all the marks I'm gonna create and * ain't gonna create no more. */ static MarkThing *alloc_mark(immortal) { int j; MarkThing *mark; if (freed_marks) /* there are marks in the free pool */ { mark = freed_marks; freed_marks = freed_marks->next; mark->mark.line = NULL; /* invalidate this mark */ mark->flags = immortal; return mark; } if (id_seed > MAX_MARKS) return NULL; /* and thats alot of marks! */ /* malloc a bunch of marks */ if (!(mark = (MarkThing *)malloc(MNX*sizeof(MarkThing)))) return NULL; for (j = MNX; j--; mark++) { mark->id = id_seed++; mark->next = freed_marks; freed_marks = mark; } /* Since I know there are marks in the free pool */ return alloc_mark(immortal); } /* Allocate n marks in the current buffer. * WARNINGs: * init_buffer_marks() must have been called on this buffer to set up * the dot and mark; * Input: * immortal : TRUE if the created mark can't be garbage collected. * Returns: * Id of the allocated mark. * -1 : Out of memory or too many marks already created. */ alloc_buffer_mark(immortal) { MarkThing *mark, *the_mark; /* Put the new mark after dot and mark */ the_mark = ((MarkThing *)(curbp->marks))->next; if (!(mark = alloc_mark(immortal))) return -1; mark->next = the_mark->next; the_mark->next = mark; return mark->id; } /* Free all marks in buffer bp. This must called as the buffer is being * freed. * Just link all the marks in a buffer onto the mark free list. * Input: * bp: pointer to the buffer being freed. */ void free_buffer_marks(bp) Buffer *bp; { register MarkThing *ptr; for (ptr = (MarkThing *)(bp->marks); ptr->next; ptr = ptr->next) ; ptr->next = freed_marks; freed_marks = (MarkThing *)(bp->marks); } /* Free a mark in a buffer. * WARNING * Mark better not be dot or mark. If it is, expect ME to throw a fit. * Mark better be a pointer to a real mark in buffer bp. If not, core * dump city. * Notes: * Since the dot is always first in the list and we never free it * (unless the buffer is freed), don't have to worry about the head * of the list (there is always a mark before the freed one). * Input: * bp : pointer to the buffer that the mark is in. * mark : pointer to the mark to be freed. */ static void free_mark(bp, mark) Buffer *bp; MarkThing *mark; { register MarkThing *ptr; for (ptr = (MarkThing *)(bp->marks); ptr->next != mark; ptr = ptr->next) ; ptr->next = mark->next; mark->next = freed_marks; freed_marks = mark; /* stash mark in the free list */ } /* Free a mark, by id, in the current buffer. * Notes: * Don't free the mark or dot - they are used internally and need to * stick around. * Gag alert! Note that this code depends on the mark being the first * thing in a MarkThing structure. Ahh, the short cuts I take to * avoid writing code I'll have to debug later. * This code is pretty slow. It takes almost two complete traversals * of the mark list to actually get the mark freed. Lucky for me, I * don't think it really matters. * Input: * mark_id: * Returns: * TRUE: OK * FALSE: Bad mark id or tried to free the dot or mark. */ free_buffer_mark(mark_id) { Mark *mark; if (mark_id == THE_DOT || mark_id == THE_MARK) return FALSE; if (!(mark = id_to_mark(mark_id))) return FALSE; free_mark(curbp, (MarkThing *)mark); return TRUE; } /* ******************************************************************** */ /* ******************************************************************** */ /* Routines to enable others to look at or modify all the marks in a * buffer with out knowing how or where they are stored. * The first buffer returned is the dot. * How to use: * zsetup_marks(); * dot = znext_mark(); * munge dot(); * while (mark = znext_mark()) munge_mark() * Notes: * It might be a good idea to skip all invalid marks but there is * probably somebody who wants to see them. */ static MarkThing *wmark, zak; Mark *znext_mark() { return &(wmark = wmark->next)->mark; } void zsetup_marks() { zak.next = (MarkThing *)(curbp->marks); wmark = &zak; } /* Garbage collect all marks in all buffers. * This means freeing all marks not marked immortal. * See the spec for MMgc_external_objects() for when and why this is * called. * Notes: * Call this after gc_buffers(). If a buffer going to be freed, I * don't want to look at the marks in it. * If there are many marks-to-be freed, calling free_mark() will waste * a LOT of time. But I suspect that most of the time there will be * no marks to free and besides, the code looks cleaner. */ void gc_marks() { Buffer *bp; MarkThing *mark, *next_mark; for (bp = first_buffer; bp; bp = bp->nextb) { for (mark = (MarkThing *)(bp->marks); mark; mark = next_mark) { next_mark = mark->next; /* since next is gone after free_mark() */ if (!(mark->flags)) free_mark(bp,mark); /* its a gonner */ } } } #if 0 /* another way to do the same thing */ /* The way I do this works because I know I'm not going to free the * first two marks (if I do, screwing up here is the least of my * worries). */ void gc_marks() { Buffer *bp; MarkThing *mark, *next_mark, *last_mark; for (bp = first_buffer; bp; bp = bp->nextb) { for (mark = (MarkThing *)(bp->marks); mark; mark = next_mark) { next_mark = mark->next; /* since next is gone after free_mark() */ if (!(mark->flags)) /* its a gonner */ { last_mark->next = next_mark; /* unlink it */ mark->next = freed_marks; /* stash mark in the free list */ freed_marks = mark; } else last_mark = mark; } } } #endif