mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			613 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			613 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 16 december 2016
 | |
| #include "../ui.h"
 | |
| #include "uipriv.h"
 | |
| #include "attrstr.h"
 | |
| 
 | |
| /*
 | |
| An attribute list is a doubly linked list of attributes.
 | |
| Attribute start positions are inclusive and attribute end positions are exclusive (or in other words, [start, end)).
 | |
| The list is kept sorted in increasing order by start position. Whether or not the sort is stable is undefined, so no temporal information should be expected to stay.
 | |
| Overlapping attributes are not allowed; if an attribute is added that conflicts with an existing one, the existing one is removed.
 | |
| In addition, the linked list tries to reduce fragmentation: if an attribute is added that just expands another, then there will only be one entry in alist, not two. (TODO does it really?)
 | |
| The linked list is not a ring; alist->fist->prev == NULL and alist->last->next == NULL.
 | |
| TODO verify that this disallows attributes of length zero
 | |
| */
 | |
| 
 | |
| struct attr {
 | |
| 	uiAttribute *val;
 | |
| 	size_t start;
 | |
| 	size_t end;
 | |
| 	struct attr *prev;
 | |
| 	struct attr *next;
 | |
| };
 | |
| 
 | |
| struct uiprivAttrList {
 | |
| 	struct attr *first;
 | |
| 	struct attr *last;
 | |
| };
 | |
| 
 | |
| // if before is NULL, add to the end of the list
 | |
| static void attrInsertBefore(uiprivAttrList *alist, struct attr *a, struct attr *before)
 | |
| {
 | |
| 	// if the list is empty, this is the first item
 | |
| 	if (alist->first == NULL) {
 | |
| 		alist->first = a;
 | |
| 		alist->last = a;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// add to the end
 | |
| 	if (before == NULL) {
 | |
| 		struct attr *oldlast;
 | |
| 
 | |
| 		oldlast = alist->last;
 | |
| 		alist->last = a;
 | |
| 		a->prev = oldlast;
 | |
| 		oldlast->next = a;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// add to the beginning
 | |
| 	if (before == alist->first) {
 | |
| 		struct attr *oldfirst;
 | |
| 
 | |
| 		oldfirst = alist->first;
 | |
| 		alist->first = a;
 | |
| 		oldfirst->prev = a;
 | |
| 		a->next = oldfirst;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	// add to the middle
 | |
| 	a->prev = before->prev;
 | |
| 	a->next = before;
 | |
| 	before->prev = a;
 | |
| 	a->prev->next = a;
 | |
| }
 | |
| 
 | |
| static int attrHasPos(struct attr *a, size_t pos)
 | |
| {
 | |
| 	if (pos < a->start)
 | |
| 		return 0;
 | |
| 	return pos < a->end;
 | |
| }
 | |
| 
 | |
| // returns 1 if there was an intersection and 0 otherwise
 | |
| static int attrRangeIntersect(struct attr *a, size_t *start, size_t *end)
 | |
| {
 | |
| 	// is the range outside a entirely?
 | |
| 	if (*start >= a->end)
 | |
| 		return 0;
 | |
| 	if (*end < a->start)
 | |
| 		return 0;
 | |
| 
 | |
| 	// okay, so there is an overlap
 | |
| 	// compute the intersection
 | |
| 	if (*start < a->start)
 | |
| 		*start = a->start;
 | |
| 	if (*end > a->end)
 | |
| 		*end = a->end;
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| // returns the old a->next, for forward iteration
 | |
| static struct attr *attrUnlink(uiprivAttrList *alist, struct attr *a)
 | |
| {
 | |
| 	struct attr *p, *n;
 | |
| 
 | |
| 	p = a->prev;
 | |
| 	n = a->next;
 | |
| 	a->prev = NULL;
 | |
| 	a->next = NULL;
 | |
| 
 | |
| 	// only item in list?
 | |
| 	if (p == NULL && n == NULL) {
 | |
| 		alist->first = NULL;
 | |
| 		alist->last = NULL;
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	// start of list?
 | |
| 	if (p == NULL) {
 | |
| 		n->prev = NULL;
 | |
| 		alist->first = n;
 | |
| 		return n;
 | |
| 	}
 | |
| 	// end of list?
 | |
| 	if (n == NULL) {
 | |
| 		p->next = NULL;
 | |
| 		alist->last = p;
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	// middle of list
 | |
| 	p->next = n;
 | |
| 	n->prev = p;
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| // returns the old a->next, for forward iteration
 | |
| static struct attr *attrDelete(uiprivAttrList *alist, struct attr *a)
 | |
| {
 | |
| 	struct attr *next;
 | |
| 
 | |
| 	next = attrUnlink(alist, a);
 | |
| 	uiprivAttributeRelease(a->val);
 | |
| 	uiprivFree(a);
 | |
| 	return next;
 | |
| }
 | |
| 
 | |
| // attrDropRange() removes attributes without deleting characters.
 | |
| // 
 | |
| // If the attribute needs no change, then nothing is done.
 | |
| // 
 | |
| // If the attribute needs to be deleted, it is deleted.
 | |
| // 
 | |
| // If the attribute only needs to be resized at the end, it is adjusted.
 | |
| // 
 | |
| // If the attribute only needs to be resized at the start, it is adjusted, unlinked, and returned in tail.
 | |
| // 
 | |
| // Otherwise, the attribute needs to be split. The existing attribute is adjusted to make the left half and a new attribute with the right half. This attribute is kept unlinked and returned in tail.
 | |
| // 
 | |
| // In all cases, the return value is the next attribute to look at in a forward sequential loop.
 | |
| static struct attr *attrDropRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end, struct attr **tail)
 | |
| {
 | |
| 	struct attr *b;
 | |
| 
 | |
| 	// always pre-initialize tail to NULL
 | |
| 	*tail = NULL;
 | |
| 
 | |
| 	if (!attrRangeIntersect(a, &start, &end))
 | |
| 		// out of range; nothing to do
 | |
| 		return a->next;
 | |
| 
 | |
| 	// just outright delete the attribute?
 | |
| 	// the inequalities handle attributes entirely inside the range
 | |
| 	// if both are equal, the attribute's range is equal to the range
 | |
| 	if (a->start >= start && a->end <= end)
 | |
| 		return attrDelete(alist, a);
 | |
| 
 | |
| 	// only chop off the start or end?
 | |
| 	if (a->start == start) {			// chop off the start
 | |
| 		// we are dropping the left half, so set a->start and unlink
 | |
| 		a->start = end;
 | |
| 		*tail = a;
 | |
| 		return attrUnlink(alist, a);
 | |
| 	}
 | |
| 	if (a->end == end) {				// chop off the end
 | |
| 		// we are dropping the right half, so just set a->end
 | |
| 		a->end = start;
 | |
| 		return a->next;
 | |
| 	}
 | |
| 
 | |
| 	// we'll need to split the attribute into two
 | |
| 	b = uiprivNew(struct attr);
 | |
| 	b->val = uiprivAttributeRetain(a->val);
 | |
| 	b->start = end;
 | |
| 	b->end = a->end;
 | |
| 	*tail = b;
 | |
| 
 | |
| 	a->end = start;
 | |
| 	return a->next;
 | |
| }
 | |
| 
 | |
| static void attrGrow(uiprivAttrList *alist, struct attr *a, size_t start, size_t end)
 | |
| {
 | |
| 	struct attr *before;
 | |
| 
 | |
| 	// adjusting the end is simple: if it ends before our new end, just set the new end
 | |
| 	if (a->end < end)
 | |
| 		a->end = end;
 | |
| 
 | |
| 	// adjusting the start is harder
 | |
| 	// if the start is before our new start, we are done
 | |
| 	// otherwise, we have to move the start back AND reposition the attribute to keep the sorted order
 | |
| 	if (a->start <= start)
 | |
| 		return;
 | |
| 	a->start = start;
 | |
| 	attrUnlink(alist, a);
 | |
| 	for (before = alist->first; before != NULL; before = before->next)
 | |
| 		if (before->start > a->start)
 | |
| 			break;
 | |
| 	attrInsertBefore(alist, a, before);
 | |
| }
 | |
| 
 | |
| // returns the right side of the split, which is unlinked, or NULL if no split was done
 | |
| static struct attr *attrSplitAt(uiprivAttrList *alist, struct attr *a, size_t at)
 | |
| {
 | |
| 	struct attr *b;
 | |
| 
 | |
| 	// no splittng needed?
 | |
| 	// note the equality: we don't need to split at start or end
 | |
| 	// in the end case, the last split point is at - 1; at itself is outside the range, and at - 1 results in the right hand side having length 1
 | |
| 	if (at <= a->start)
 | |
| 		return NULL;
 | |
| 	if (at >= a->end)
 | |
| 		return NULL;
 | |
| 
 | |
| 	b = uiprivNew(struct attr);
 | |
| 	b->val = uiprivAttributeRetain(a->val);
 | |
| 	b->start = at;
 | |
| 	b->end = a->end;
 | |
| 
 | |
| 	a->end = at;
 | |
| 	return b;
 | |
| }
 | |
| 
 | |
| // attrDeleteRange() removes attributes while deleting characters.
 | |
| // 
 | |
| // If the attribute does not include the deleted range, then nothing is done (though the start and end are adjusted as necessary).
 | |
| // 
 | |
| // If the attribute needs to be deleted, it is deleted.
 | |
| // 
 | |
| // Otherwise, the attribute only needs the start or end deleted, and it is adjusted.
 | |
| // 
 | |
| // In all cases, the return value is the next attribute to look at in a forward sequential loop.
 | |
| // TODO rewrite this comment
 | |
| static struct attr *attrDeleteRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end)
 | |
| {
 | |
| 	size_t ostart, oend;
 | |
| 	size_t count;
 | |
| 
 | |
| 	ostart = start;
 | |
| 	oend = end;
 | |
| 	count = oend - ostart;
 | |
| 
 | |
| 	if (!attrRangeIntersect(a, &start, &end)) {
 | |
| 		// out of range
 | |
| 		// adjust if necessary
 | |
| 		if (a->start >= ostart)
 | |
| 			a->start -= count;
 | |
| 		if (a->end >= oend)
 | |
| 			a->end -= count;
 | |
| 		return a->next;
 | |
| 	}
 | |
| 
 | |
| 	// just outright delete the attribute?
 | |
| 	// the inequalities handle attributes entirely inside the range
 | |
| 	// if both are equal, the attribute's range is equal to the range
 | |
| 	if (a->start >= start && a->end <= end)
 | |
| 		return attrDelete(alist, a);
 | |
| 
 | |
| 	// only chop off the start or end?
 | |
| 	if (a->start == start) {			// chop off the start
 | |
| 		// if we weren't adjusting positions this would just be setting a->start to end
 | |
| 		// but since this is deleting from the start, we need to adjust both by count
 | |
| 		a->start = end - count;
 | |
| 		a->end -= count;
 | |
| 		return a->next;
 | |
| 	}
 | |
| 	if (a->end == end) {				// chop off the end
 | |
| 		// a->start is already good
 | |
| 		a->end = start;
 | |
| 		return a->next;
 | |
| 	}
 | |
| 
 | |
| 	// in this case, the deleted range is inside the attribute
 | |
| 	// we can clear it by just removing count from a->end
 | |
| 	a->end -= count;
 | |
| 	return a->next;
 | |
| }
 | |
| 
 | |
| uiprivAttrList *uiprivNewAttrList(void)
 | |
| {
 | |
| 	return uiprivNew(uiprivAttrList);
 | |
| }
 | |
| 
 | |
| void uiprivFreeAttrList(uiprivAttrList *alist)
 | |
| {
 | |
| 	struct attr *a, *next;
 | |
| 
 | |
| 	a = alist->first;
 | |
| 	while (a != NULL) {
 | |
| 		next = a->next;
 | |
| 		uiprivAttributeRelease(a->val);
 | |
| 		uiprivFree(a);
 | |
| 		a = next;
 | |
| 	}
 | |
| 	uiprivFree(alist);
 | |
| }
 | |
| 
 | |
| void uiprivAttrListInsertAttribute(uiprivAttrList *alist, uiAttribute *val, size_t start, size_t end)
 | |
| {
 | |
| 	struct attr *a;
 | |
| 	struct attr *before;
 | |
| 	struct attr *tail = NULL;
 | |
| 	int split = 0;
 | |
| 	uiAttributeType valtype;
 | |
| 
 | |
| 	// first, figure out where in the list this should go
 | |
| 	// in addition, if this attribute overrides one that already exists, split that one apart so this one can take over
 | |
| 	before = alist->first;
 | |
| 	valtype = uiAttributeGetType(val);
 | |
| 	while (before != NULL) {
 | |
| 		size_t lstart, lend;
 | |
| 
 | |
| 		// once we get to the first point after start, we know where to insert
 | |
| 		if (before->start > start)
 | |
| 			break;
 | |
| 
 | |
| 		// if we have already split a prior instance of this attribute, don't bother doing it again
 | |
| 		if (split)
 | |
| 			goto next;
 | |
| 
 | |
| 		// should we split this attribute?
 | |
| 		if (uiAttributeGetType(before->val) != valtype)
 | |
| 			goto next;
 | |
| 		lstart = start;
 | |
| 		lend = end;
 | |
| 		if (!attrRangeIntersect(before, &lstart, &lend))
 | |
| 			goto next;
 | |
| 
 | |
| 		// okay so this might conflict; if the val is the same as the one we want, we need to expand the existing attribute, not fragment anything
 | |
| 		// TODO will this reduce fragmentation if we first add from 0 to 2 and then from 2 to 4? or do we have to do that separately?
 | |
| 		if (uiprivAttributeEqual(before->val, val)) {
 | |
| 			attrGrow(alist, before, start, end);
 | |
| 			return;
 | |
| 		}
 | |
| 		// okay the values are different; we need to split apart
 | |
| 		before = attrDropRange(alist, before, start, end, &tail);
 | |
| 		split = 1;
 | |
| 		continue;
 | |
| 
 | |
| 	next:
 | |
| 		before = before->next;
 | |
| 	}
 | |
| 
 | |
| 	// if we got here, we know we have to add the attribute before before
 | |
| 	a = uiprivNew(struct attr);
 | |
| 	a->val = uiprivAttributeRetain(val);
 | |
| 	a->start = start;
 | |
| 	a->end = end;
 | |
| 	attrInsertBefore(alist, a, before);
 | |
| 
 | |
| 	// and finally, if we split, insert the remainder
 | |
| 	if (tail == NULL)
 | |
| 		return;
 | |
| 	// note we start at before; it won't be inserted before that by the sheer nature of how the code above works
 | |
| 	for (; before != NULL; before = before->next)
 | |
| 		if (before->start > tail->start)
 | |
| 			break;
 | |
| 	attrInsertBefore(alist, tail, before);
 | |
| }
 | |
| 
 | |
| void uiprivAttrListInsertCharactersUnattributed(uiprivAttrList *alist, size_t start, size_t count)
 | |
| {
 | |
| 	struct attr *a;
 | |
| 	struct attr *tails = NULL;
 | |
| 
 | |
| 	// every attribute before the insertion point can either cross into the insertion point or not
 | |
| 	// if it does, we need to split that attribute apart at the insertion point, keeping only the old attribute in place, adjusting the new tail, and preparing it for being re-added later
 | |
| 	for (a = alist->first; a != NULL; a = a->next) {
 | |
| 		struct attr *tail;
 | |
| 
 | |
| 		// stop once we get to the insertion point
 | |
| 		if (a->start >= start)
 | |
| 			break;
 | |
| 		// only do something if overlapping
 | |
| 		if (!attrHasPos(a, start))
 | |
| 			continue;
 | |
| 
 | |
| 		tail = attrSplitAt(alist, a, start);
 | |
| 		// adjust the new tail for the insertion
 | |
| 		tail->start += count;
 | |
| 		tail->end += count;
 | |
| 		// and queue it for re-adding later
 | |
| 		// we can safely use tails as if it was singly-linked since it's just a temporary list; we properly merge them back in below and they'll be doubly-linked again then
 | |
| 		// TODO actually we could probably save some time by making then doubly-linked now and adding them in one fell swoop, but that would make things a bit more complicated...
 | |
| 		tail->next = tails;
 | |
| 		tails = tail;
 | |
| 	}
 | |
| 
 | |
| 	// at this point in the attribute list, we are at or after the insertion point
 | |
| 	// all the split-apart attributes will be at the insertion point
 | |
| 	// therefore, we can just add them all back now, and the list will still be sorted correctly
 | |
| 	while (tails != NULL) {
 | |
| 		struct attr *next;
 | |
| 
 | |
| 		// make all the links NULL before insertion, just to be safe
 | |
| 		next = tails->next;
 | |
| 		tails->next = NULL;
 | |
| 		attrInsertBefore(alist, tails, a);
 | |
| 		tails = next;
 | |
| 	}
 | |
| 
 | |
| 	// every remaining attribute will be either at or after the insertion point
 | |
| 	// we just need to move them ahead
 | |
| 	for (; a != NULL; a = a->next) {
 | |
| 		a->start += count;
 | |
| 		a->end += count;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // The attributes are those of character start - 1.
 | |
| // If start == 0, the attributes are those of character 0.
 | |
| /*
 | |
| This is an obtuse function. Here's some diagrams to help.
 | |
| 
 | |
| Given the input string
 | |
| 	abcdefghi (positions: 012345678 9)
 | |
| and attribute set
 | |
| 	red start 0 end 3
 | |
| 	bold start 2 end 6
 | |
| 	underline start 5 end 8
 | |
| or visually:
 | |
| 	012345678 9
 | |
| 	rrr------
 | |
| 	--bbbb---
 | |
| 	-----uuu-
 | |
| If we insert qwe to result in positions 0123456789AB C:
 | |
| 
 | |
| before 0, 1, 2 (grow the red part, move everything else down)
 | |
| 	red -> start 0 (no change) end 3+3=6
 | |
| 	bold -> start 2+3=5 end 6+3=9
 | |
| 	underline -> start 5+3=8 end 8+3=B
 | |
| before 3 (grow red and bold, move underline down)
 | |
| 	red -> start 0 (no change) end 3+3=6
 | |
| 	bold -> start 2 (no change) end 6+3=9
 | |
| 	underline -> start 5+3=8 end 8+3=B
 | |
| before 4, 5 (keep red, grow bold, move underline down)
 | |
| 	red -> start 0 (no change) end 3 (no change)
 | |
| 	bold -> start 2 (no change) end 6+3=9
 | |
| 	underline -> start 5+3=8 end 8+3=B
 | |
| before 6 (keep red, grow bold and underline)
 | |
| 	red -> start 0 (no change) end 3 (no change)
 | |
| 	bold -> start 2 (no change) end 6+3=9
 | |
| 	underline -> start 5 (no change) end 8+3=B
 | |
| before 7, 8 (keep red and bold, grow underline)
 | |
| 	red -> start 0 (no change) end 3 (no change)
 | |
| 	bold -> start 2 (no change) end 6 (no change)
 | |
| 	underline -> start 5 (no change) end 8+3=B
 | |
| before 9 (keep all three)
 | |
| 	red -> start 0 (no change) end 3 (no change)
 | |
| 	bold -> start 2 (no change) end 6 (no change)
 | |
| 	underline -> start 5 (no change) end 8 (no change)
 | |
| 
 | |
| result:
 | |
|           0 1 2 3 4 5 6 7 8 9
 | |
|       red E E E e n n n n n n
 | |
|      bold s s S E E E e n n n
 | |
| underline s s s s s S E E e n
 | |
| N = none
 | |
| E = end only
 | |
| S = start and end
 | |
| uppercase = in original range, lowercase = not
 | |
| 
 | |
| which results in our algorithm:
 | |
| 	for each attribute
 | |
| 		if start < insertion point
 | |
| 			move start up
 | |
| 		else if start == insertion point
 | |
| 			if start != 0
 | |
| 				move start up
 | |
| 		if end <= insertion point
 | |
| 			move end up
 | |
| */
 | |
| // TODO does this ensure the list remains sorted?
 | |
| void uiprivAttrListInsertCharactersExtendingAttributes(uiprivAttrList *alist, size_t start, size_t count)
 | |
| {
 | |
| 	struct attr *a;
 | |
| 
 | |
| 	for (a = alist->first; a != NULL; a = a->next) {
 | |
| 		if (a->start < start)
 | |
| 			a->start += count;
 | |
| 		else if (a->start == start && start != 0)
 | |
| 			a->start += count;
 | |
| 		if (a->end <= start)
 | |
| 			a->end += count;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TODO replace at point with — replaces with first character's attributes
 | |
| 
 | |
| void uiprivAttrListRemoveAttribute(uiprivAttrList *alist, uiAttributeType type, size_t start, size_t end)
 | |
| {
 | |
| 	struct attr *a;
 | |
| 	struct attr *tails = NULL;		// see uiprivAttrListInsertCharactersUnattributed() above
 | |
| 	struct attr *tailsAt = NULL;
 | |
| 
 | |
| 	a = alist->first;
 | |
| 	while (a != NULL) {
 | |
| 		size_t lstart, lend;
 | |
| 		struct attr *tail;
 | |
| 
 | |
| 		// this defines where to re-attach the tails
 | |
| 		// (all the tails will have their start at end, so we can just insert them all before tailsAt)
 | |
| 		if (a->start >= end) {
 | |
| 			tailsAt = a;
 | |
| 			// and at this point we're done, so
 | |
| 			break;
 | |
| 		}
 | |
| 		if (uiAttributeGetType(a->val) != type)
 | |
| 			goto next;
 | |
| 		lstart = start;
 | |
| 		lend = end;
 | |
| 		if (!attrRangeIntersect(a, &lstart, &lend))
 | |
| 			goto next;
 | |
| 		a = attrDropRange(alist, a, start, end, &tail);
 | |
| 		if (tail != NULL) {
 | |
| 			tail->next = tails;
 | |
| 			tails = tail;
 | |
| 		}
 | |
| 		continue;
 | |
| 
 | |
| 	next:
 | |
| 		a = a->next;
 | |
| 	}
 | |
| 
 | |
| 	while (tails != NULL) {
 | |
| 		struct attr *next;
 | |
| 
 | |
| 		// make all the links NULL before insertion, just to be safe
 | |
| 		next = tails->next;
 | |
| 		tails->next = NULL;
 | |
| 		attrInsertBefore(alist, tails, a);
 | |
| 		tails = next;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TODO merge this with the above
 | |
| void uiprivAttrListRemoveAttributes(uiprivAttrList *alist, size_t start, size_t end)
 | |
| {
 | |
| 	struct attr *a;
 | |
| 	struct attr *tails = NULL;		// see uiprivAttrListInsertCharactersUnattributed() above
 | |
| 	struct attr *tailsAt = NULL;
 | |
| 
 | |
| 	a = alist->first;
 | |
| 	while (a != NULL) {
 | |
| 		size_t lstart, lend;
 | |
| 		struct attr *tail;
 | |
| 
 | |
| 		// this defines where to re-attach the tails
 | |
| 		// (all the tails will have their start at end, so we can just insert them all before tailsAt)
 | |
| 		if (a->start >= end) {
 | |
| 			tailsAt = a;
 | |
| 			// and at this point we're done, so
 | |
| 			break;
 | |
| 		}
 | |
| 		lstart = start;
 | |
| 		lend = end;
 | |
| 		if (!attrRangeIntersect(a, &lstart, &lend))
 | |
| 			goto next;
 | |
| 		a = attrDropRange(alist, a, start, end, &tail);
 | |
| 		if (tail != NULL) {
 | |
| 			tail->next = tails;
 | |
| 			tails = tail;
 | |
| 		}
 | |
| 		continue;
 | |
| 
 | |
| 	next:
 | |
| 		a = a->next;
 | |
| 	}
 | |
| 
 | |
| 	while (tails != NULL) {
 | |
| 		struct attr *next;
 | |
| 
 | |
| 		// make all the links NULL before insertion, just to be safe
 | |
| 		next = tails->next;
 | |
| 		tails->next = NULL;
 | |
| 		attrInsertBefore(alist, tails, a);
 | |
| 		tails = next;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void uiprivAttrListRemoveCharacters(uiprivAttrList *alist, size_t start, size_t end)
 | |
| {
 | |
| 	struct attr *a;
 | |
| 
 | |
| 	a = alist->first;
 | |
| 	while (a != NULL)
 | |
| 		a = attrDeleteRange(alist, a, start, end);
 | |
| }
 | |
| 
 | |
| void uiprivAttrListForEach(const uiprivAttrList *alist, const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)
 | |
| {
 | |
| 	struct attr *a;
 | |
| 	uiForEach ret;
 | |
| 
 | |
| 	for (a = alist->first; a != NULL; a = a->next) {
 | |
| 		ret = (*f)(s, a->val, a->start, a->end, data);
 | |
| 		if (ret == uiForEachStop)
 | |
| 			// TODO for all: break or return?
 | |
| 			break;
 | |
| 	}
 | |
| }
 |