1 /* Bluefish HTML Editor
2  * bftextview2_markregion.c
3  *
4  * Copyright (C) 2013 Olivier Sessink
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /* markregion design:
21 
22 the markregion code is used to mark which characters of the BluefishTextView need
23 to be re-scanned for syntax, or for spell checking. Each document has a
24 separate Tregions structure for syntax scanning and spell checking in BluefishTextView
25 
26 The Tregions simply point to a head and tail of a double linked list of Tchange elements.
27 
28 After syntax scanning is complete and spell checking is complete these regions are empty.
29 
30 So usually the list is very short, and if the list is long (for example after tabs-to-spaces
31 or another function that does many text changes in one go) it is usually only appended to
32 because these functions start in the beginning of the text and work towards the end. That's
33 why a linked list is good enough, and a balanced tree would probably be worse in performance.
34 
35 The list has two entries for each sequence of characters that need rescanning/spellchecking,
36 one entry positions the start, the second entry positions the end.
37 */
38 
39 #include "bluefish.h"
40 #include "bf_lib.h"
41 #include "bftextview2_private.h"
42 #include "bftextview2_markregion.h"
43 
44 #ifdef MARKREGION
45 /*#define PROFILE_MARKREGION*/
46 
47 #ifdef PROFILE_MARKREGION
48 typedef struct {
49 	gint num_list_walks;
50 	gint num_region_changes;
51 	gint num_offset_updates;
52 	gint num_new;
53 	gint num_full_append;
54 	gint num_full_prepend;
55 	gint num_insert;
56 	gint num_allocs;
57 } Tprofile_markregion;
58 Tprofile_markregion prof = {0,0,0,0,0,0,0,0};
59 #endif
60 
61 typedef struct {
62 	BF_ELIST_HEAD;
63 	guint32 pos;
64 	guint32 is_start; /* a guint8 would have been good enough, but
65 								both on 32bit or 64 bit systems that doesn't affect
66 								the size of Tchange, and this is faster on 32bit
67 								systems */
68 } Tchange;
69 #define CHANGE(var) ((Tchange *)var)
70 
71 #ifdef DEVELOPMENT
72 #ifdef NEEDSCANNING
73 void
compare_markregion_needscanning(BluefishTextView * btv)74 compare_markregion_needscanning(BluefishTextView *btv) {
75 	gboolean cont=TRUE;
76 	Tchange *cso, *ceo;
77 	guint so,eo;
78 	GtkTextIter start,end;
79 	DBG_MARKREGION("compare_markregion_needscanning, started, markregion has head(%d)|tail(%d)\n",btv->scanning.head?CHANGE(btv->scanning.head)->pos:-1
80 						,btv->scanning.tail?CHANGE(btv->scanning.tail)->pos:-1);
81 	gtk_text_buffer_get_start_iter(btv->buffer, &start);
82 	cso = CHANGE(btv->scanning.head);
83 	while (cont) {
84 		if (!gtk_text_iter_begins_tag(&start, btv->needscanning)) {
85 			if (!gtk_text_iter_forward_to_tag_toggle(&start, btv->needscanning)) {
86 				cont=FALSE;
87 				if (cso) {
88 					g_print("ABORT: needscanning has no further region, markregion has cso=%d, ceo=%d\n",cso->pos, cso->next?CHANGE(cso->next)->pos:-1);
89 					g_assert_not_reached();
90 				}
91 				break;
92 			}
93 		}
94 		end = start;
95 		gtk_text_iter_forward_char(&end);
96 		if (!gtk_text_iter_ends_tag(&end, btv->needscanning)) {
97 			if (!gtk_text_iter_forward_to_tag_toggle(&end, btv->needscanning)) {
98 				g_print("huh2?\n");
99 			}
100 		}
101 		ceo = CHANGE(cso->next);
102 		so = gtk_text_iter_get_offset(&start);
103 		eo = gtk_text_iter_get_offset(&end);
104 		if(so != cso->pos || eo != ceo->pos) {
105 			g_print("ABORT: needscanning has %u:%u, markregion has %u:%u\n",so,eo,cso->pos,ceo->pos);
106 			g_assert_not_reached();
107 		}
108 /*		DBG_MARKREGION("region %u:%u\n",so,eo);*/
109 		start = end;
110 		cso = ceo->next;
111 		cont = gtk_text_iter_forward_char(&start);
112 		if (!cont && cso) {
113 			g_print("ABORT: buffer has no further characters, markregion has cso=%d, ceo=%d\n",cso->pos, cso->next?CHANGE(cso->next)->pos:-1);
114 			g_assert_not_reached();
115 		}
116 	}
117 /*	g_print("*****\n");*/
118 }
119 #endif
120 
121 static void
markregion_verify_integrity(Tregions * rg)122 markregion_verify_integrity(Tregions *rg)
123 {
124 	Tchange *end, *start = rg->head;
125 	DBG_MARKREGION("markregion_verify_integrity, started, head(%d)|tail(%d)\n",rg->head?CHANGE(rg->head)->pos:-1
126 						,rg->tail?CHANGE(rg->tail)->pos:-1);
127 	if (!rg->head) {
128 		if (rg->tail || rg->last) {
129 			g_print("ERROR: markregion_verify_integrity, !rg->head but there is a tail (%p) or last (%p) !?!?!\n",rg->tail, rg->last);
130 			g_assert_not_reached();
131 		}
132 		return;
133 	}
134 
135 /*	g_print("* * * * *\n");*/
136 	if (CHANGE(rg->head)->prev) {
137 		g_print("ERROR: markregion_verify_integrity, rg->head has a previous entry !?!?!\n");
138 		g_assert_not_reached();
139 	}
140 	while (start) {
141 		if (start->is_start ==FALSE) {
142 			g_print("ERROR: markregion_verify_integrity, entry(%u)->is_start==FALSE but should start a region?!?\n",start->pos);
143 			g_assert_not_reached();
144 		}
145 		end = start->next;
146 		if (!end) {
147 			g_print("ERROR: markregion_verify_integrity, entry(%u) does not have an end?!?\n",start->pos);
148 			g_assert_not_reached();
149 		}
150 		if (end->prev != start) {
151 			g_print("ERROR: markregion_verify_integrity, end(%u)->prev (%p) does not point to start(%u) (%p)?!?\n",end->pos,end->prev,start->pos,start);
152 			g_assert_not_reached();
153 		}
154 /*		g_print("verifying region %u:%u\n",start->pos,end->pos);*/
155 
156 		if (end->is_start == TRUE) {
157 			g_print("ERROR: markregion_verify_integrity, entry(%u)->is_start==TRUE but should end a region?!?\n",end->pos);
158 			g_assert_not_reached();
159 		}
160 		if (start->pos >= end->pos) {
161 			g_print("ERROR: markregion_verify_integrity, start at %u but end at %u ?!?\n",start->pos,end->pos);
162 			g_assert_not_reached();
163 		}
164 		start = end->next;
165 		if (start) {
166 			if (start->prev != end) {
167 				g_print("ERROR: markregion_verify_integrity, start->prev does not point to previous end?!?\n");
168 				g_assert_not_reached();
169 			}
170 			if (end->pos >= start->pos) {
171 				g_print("ERROR: markregion_verify_integrity, next start at %u but last end at %u ?!?\n",start->pos,end->pos);
172 				g_assert_not_reached();
173 			}
174 		} else {
175 			if (end != rg->tail) {
176 				g_print("ERROR: markregion_verify_integrity, rg->tail(%u) does not equal the last entry %u ?!?\n",CHANGE(rg->tail)->pos, end->pos);
177 				g_assert_not_reached();
178 			}
179 
180 		}
181 	}
182 /*	g_print("* * * * *\n");*/
183 }
184 #endif
185 
186 static Tchange *
new_change(guint pos,gboolean is_start)187 new_change(guint pos, gboolean is_start)
188 {
189 	Tchange *change = g_slice_new(Tchange);
190 	change->next = change->prev = NULL;
191 	change->pos = pos;
192 	change->is_start = is_start;
193 #ifdef PROFILE_MARKREGION
194 	prof.num_allocs++;
195 #endif
196 	return change;
197 }
198 
199 /*
200 markregion_region_done is called after a scanning run, it is called with the position up to the point
201 where scanning was finished.
202 */
203 void
markregion_region_done(Tregions * rg,guint end)204 markregion_region_done(Tregions *rg, guint end)
205 {
206 	Tchange *tmp;
207 #ifdef PROFILE_MARKREGION
208 	prof.num_region_changes++;
209 #endif
210 	DBG_MARKREGION("markregion_region_done, end=%u\n",end);
211 	if (!rg->head) {
212 		return;
213 	}
214 	tmp = rg->head;
215 	while (tmp && tmp->pos <= end) {
216 		Tchange *toremove = tmp;
217 		DBG_MARKREGION("markregion_region_done, remove change with pos=%u\n",tmp->pos);
218 		tmp = CHANGE(bf_elist_remove(BF_ELIST(tmp))); /* returns the previous entry if that exists, but
219 										it does not exist in this case because we remove all entries */
220 		g_slice_free(Tchange, toremove);
221 #ifdef PROFILE_MARKREGION
222 		prof.num_allocs--;
223 		prof.num_list_walks++;
224 #endif
225 	}
226 	if (tmp && tmp->is_start == FALSE) {
227 		/* we cannot begin our list of regions with an end-of-region, so remove it, or prepend a start position in front of it */
228 		DBG_MARKREGION("next change at %u is a end!\n",tmp->pos);
229 		if (tmp->pos == end) {
230 			Tchange *toremove = tmp;
231 			DBG_MARKREGION("markregion_region_done, remove change with pos=%u\n",toremove->pos);
232 			tmp = CHANGE(bf_elist_remove(BF_ELIST(toremove)));
233 #ifdef PROFILE_MARKREGION
234 			prof.num_allocs--;
235 #endif
236 			g_slice_free(Tchange, toremove);
237 		} else {
238 			DBG_MARKREGION("markregion_region_done, prepend change with end=%u\n",end);
239 			tmp = CHANGE(bf_elist_prepend(BF_ELIST(tmp), BF_ELIST(new_change(end, TRUE))));
240 		}
241 	} else {
242 #ifdef DEVELOPMENT
243 		if (tmp) {
244 			DBG_MARKREGION("markregion_region_done, keep %u, is_start=%d\n",tmp->pos,tmp->is_start);
245 		}
246 #endif
247 	}
248 	rg->head = tmp;
249 	rg->last = NULL;
250 	if (tmp == NULL) {
251 		rg->tail = NULL;
252 	}
253 	DBG_MARKREGION("markregion_region_done, return, head(%d)|tail(%d)\n",rg->head?CHANGE(rg->head)->pos:-1
254 						,rg->tail?CHANGE(rg->tail)->pos:-1);
255 #ifdef PROFILE_MARKREGION
256 	g_print("markregion profiling: changes:%d, allocs=%d, offset_updates=%d, list_walks=%d, new/append/prepend/insert=%f/%f/%f/%f\n",
257 				prof.num_region_changes, prof.num_allocs, prof.num_offset_updates, prof.num_list_walks,
258 				100.0 * prof.num_new / prof.num_region_changes,
259 				100.0 * prof.num_full_append / prof.num_region_changes,
260 				100.0 * prof.num_full_prepend / prof.num_region_changes,
261 				100.0 * prof.num_insert / prof.num_region_changes
262 				 );
263 #endif
264 }
265 
266 static void
update_offset(Tchange * start,gint offset)267 update_offset(Tchange *start, gint offset)
268 {
269 	if (offset == 0)
270 		return;
271 #ifdef PROFILE_MARKREGION
272 	prof.num_offset_updates++;
273 #endif
274 	while (start) {
275 		start->pos += offset;
276 		start = start->next;
277 #ifdef PROFILE_MARKREGION
278 		prof.num_list_walks++;
279 #endif
280 	}
281 }
282 
283 static Tchange *
find_prev_or_equal_position(Tregions * rg,guint position)284 find_prev_or_equal_position(Tregions *rg, guint position)
285 {
286 	if (!rg->last) {
287 		if (position - CHANGE(rg->head)->pos < CHANGE(rg->tail)->pos - position) {
288 			rg->last = rg->head;
289 		} else {
290 			rg->last = rg->tail;
291 		}
292 	}
293 #ifdef DEVELOPMENT
294 	if (!rg->last) {
295 		g_print("ABORT: find_prev_or_equal_position, rg->last==NULL ?!?!?!\n");
296 		g_assert_not_reached();
297 	}
298 #endif
299 	if (CHANGE(rg->last)->pos == position)
300 		return CHANGE(rg->last);
301 
302 	if (CHANGE(rg->last)->pos > position) {
303 /*		DBG_MARKREGION("find_prev_or_equal_position, current position %u is beyond the requested position %u\n",CHANGE(rg->last)->pos,position);*/
304 		while (rg->last && CHANGE(rg->last)->pos > position) {
305 /*			DBG_MARKREGION("backward start %p(%u) to %p\n",CHANGE(rg->last),CHANGE(rg->last)->pos,CHANGE(rg->last)->prev);*/
306 			rg->last = CHANGE(rg->last)->prev;
307 #ifdef PROFILE_MARKREGION
308 			prof.num_list_walks++;
309 #endif
310 		}
311 	} else {
312 		Tchange *next = CHANGE(rg->last)->next;
313 /*		DBG_MARKREGION("find_prev_or_equal_position, current position %u is before the requested position %u\n",CHANGE(rg->last)->pos,position);*/
314 		while (next && next->pos <= position) {
315 /*			DBG_MARKREGION("forward start %p(%u) to %p(%u), next->next=%p\n",CHANGE(rg->last),CHANGE(rg->last)->pos,next,next->pos,next->next);*/
316 			rg->last = next;
317 			next = next->next;
318 #ifdef PROFILE_MARKREGION
319 			prof.num_list_walks++;
320 #endif
321 		}
322 	}
323 	DBG_MARKREGION("find_prev_or_equal_position, requested position %u, returning change->pos=%d\n",position,rg->last?CHANGE(rg->last)->pos:-1);
324 	return CHANGE(rg->last);
325 }
326 
327 /* both delete and insert can be handled in a generic way if the new region is either the
328 only region, beyond the end, or before the beginning. */
329 static gboolean
markregion_handle_generic(Tregions * rg,guint markstart,guint markend,guint comparepos,gint offset)330 markregion_handle_generic(Tregions *rg, guint markstart, guint markend, guint comparepos, gint offset)
331 {
332 	if (!rg->head) {
333 		/* empty region, just append the start and end */
334 		DBG_MARKREGION("markregion_handle_generic, empty, just add the first entries\n");
335 		rg->head = new_change(markstart, TRUE);
336 		rg->last = rg->tail = bf_elist_append(BF_ELIST(rg->head), BF_ELIST(new_change(markend, FALSE)));
337 #ifdef PROFILE_MARKREGION
338 		prof.num_new++;
339 #endif
340 		return TRUE;
341 	}
342 	if (CHANGE(rg->tail)->pos < markstart) {
343 		DBG_MARKREGION("markregion_handle_generic, markstart (%u) beyond the end (%u), append new entries\n", markstart, CHANGE(rg->tail)->pos);
344 		/* the new region is beyond the end */
345 		rg->last = rg->tail = bf_elist_append(BF_ELIST(rg->tail), BF_ELIST(new_change(markstart, TRUE)));
346 		rg->tail = bf_elist_append(BF_ELIST(rg->tail), BF_ELIST(new_change(markend, FALSE)));
347 #ifdef PROFILE_MARKREGION
348 		prof.num_full_append++;
349 #endif
350 		return TRUE;
351 	}
352 	if (CHANGE(rg->head)->pos > comparepos) {
353 		Tchange *oldhead = rg->head;
354 		DBG_MARKREGION("markregion_handle_generic, comparepos (%u) before the head (%u), prepend new entries and update offsets\n",comparepos,CHANGE(rg->head)->pos);
355 		/* the first region is before the current start */
356 		rg->last = rg->head = bf_elist_prepend(BF_ELIST(rg->head), BF_ELIST(new_change(markend, FALSE)));
357 		rg->head = bf_elist_prepend(BF_ELIST(rg->head), BF_ELIST(new_change(markstart, TRUE)));
358 		update_offset(oldhead, offset);
359 #ifdef PROFILE_MARKREGION
360 		prof.num_full_prepend++;
361 #endif
362 		return TRUE;
363 	}
364 	return FALSE;
365 }
366 /*
367 markregion_insert is called from the text insert callback
368 */
369 void
markregion_insert(Tregions * rg,guint markstart,guint markend)370 markregion_insert(Tregions *rg, guint markstart, guint markend)
371 {
372 	gint offset = markend-markstart;
373 #ifdef PROFILE_MARKREGION
374 	prof.num_region_changes++;
375 #endif
376 #ifdef DEVELOPMENT
377 	markregion_verify_integrity(rg);
378 	if (markstart > markend) {
379 		g_print("ABORT: markregion_insert, markstart > markend ?!?!?!\n");
380 		g_assert_not_reached();
381 	}
382 #endif
383 	DBG_MARKREGION("markregion_insert, markstart=%u, markend=%u, offset=%d\n",markstart,markend,offset);
384 	if (markstart == markend)
385 		return;
386 
387 	if (markregion_handle_generic(rg, markstart, markend, markstart, offset))
388 		return;
389 
390 #ifdef PROFILE_MARKREGION
391 		prof.num_insert++;
392 #endif
393 
394 	/* insert somewhere within the existing regions */
395 	rg->last = find_prev_or_equal_position(rg, markstart);
396 	if (!rg->last && CHANGE(rg->head)->pos > markstart) {
397 		rg->head = rg->last = bf_elist_prepend(BF_ELIST(rg->head), BF_ELIST(new_change(markstart, TRUE)));
398 	}
399 	if (CHANGE(rg->last)->is_start == TRUE) {
400 		/* only update the offset */
401 		DBG_MARKREGION("update offset, starting at %u\n",CHANGE(CHANGE(rg->last)->next)->pos);
402 		update_offset(CHANGE(rg->last)->next, offset);
403 		return;
404 	}
405 	if (CHANGE(rg->last)->pos == markstart) {
406 		Tchange *toremove;
407 		/* out current start previously ended a region, merge the old and new regions together */
408 		toremove = rg->last;
409  		rg->last = bf_elist_remove(toremove); /* returns 'change->prev' if there is one */
410  		g_slice_free(Tchange,toremove);
411 #ifdef PROFILE_MARKREGION
412 		prof.num_allocs--;
413 #endif
414 		rg->last = bf_elist_append(BF_ELIST(rg->last), BF_ELIST(new_change(markend, FALSE)));
415 		if (CHANGE(rg->last)->next) {
416 			update_offset(CHANGE(rg->last)->next, offset);
417 		} else {
418 			rg->tail = rg->last;
419 		}
420 		return;
421 	}
422 	if (CHANGE(rg->last)->pos < markstart) {
423 		rg->last = bf_elist_append(BF_ELIST(rg->last), BF_ELIST(new_change(markstart, TRUE)));
424 		rg->last = bf_elist_append(BF_ELIST(rg->last), BF_ELIST(new_change(markend, FALSE)));
425 		update_offset(CHANGE(rg->last)->next, offset);
426 		return;
427 	}
428 #ifdef DEVELOPMENT
429 	g_print("ABORT: markregion_insert, we should never get to the end of this function\n");
430 	g_assert_not_reached();
431 #endif
432 }
433 
434 
435 /*
436 markregion_delete is called from the text delete callback
437 the markend parameter in markregion_delete should be already corrected for the offset.
438 */
439 void
markregion_delete(Tregions * rg,guint markstart,guint markend,gint offset)440 markregion_delete(Tregions *rg, guint markstart, guint markend, gint offset)
441 {
442 	gint comparepos = markend-offset;
443 	Tchange *oldlast;
444 #ifdef PROFILE_MARKREGION
445 	prof.num_region_changes++;
446 #endif
447 
448 #ifdef DEVELOPMENT
449 	markregion_verify_integrity(rg);
450 	if (markstart > markend) {
451 		g_print("ABORT: markregion_insert, markstart > markend ?!?!?!\n");
452 		g_assert_not_reached();
453 	}
454 #endif
455 	DBG_MARKREGION("markregion_delete, %u:%u, update offset with %d, comparepos=%d. head(%d)|tail(%d)\n",markstart,markend,offset,comparepos
456 						,rg->head?CHANGE(rg->head)->pos:-1
457 						,rg->tail?CHANGE(rg->tail)->pos:-1);
458 	if (markstart == 0 && markend == 0 && rg->tail) {
459 		rg->last = rg->head;
460 		while(rg->last && CHANGE(rg->last)->pos <= comparepos) {
461 			Tchange *toremove = CHANGE(rg->last);
462 			rg->last = CHANGE(rg->last)->next;
463 			bf_elist_remove(BF_ELIST(toremove));
464 			DBG_MARKREGION("markregion_delete, remove pos=%u, because it is lower than %d\n",toremove->pos,comparepos);
465 			g_slice_free(Tchange, toremove);
466 #ifdef PROFILE_MARKREGION
467 			prof.num_allocs--;
468 #endif
469 		}
470 		rg->head = rg->last;
471 		if (!rg->last) {
472 			rg->tail = NULL;
473 		}
474 	}
475 	if (markstart == markend) {
476 		 return;
477 	}
478 
479 	if (markregion_handle_generic(rg, markstart, markend, comparepos, offset))
480 		return;
481 
482 #ifdef PROFILE_MARKREGION
483 		prof.num_insert++;
484 #endif
485 
486 	rg->last = find_prev_or_equal_position(rg, markstart);
487 	if (rg->last == NULL) {
488 		/* starts before head */
489 		DBG_MARKREGION("markregion_delete, prepend start %u to head (%u)\n",markstart,rg->head?CHANGE(rg->head)->pos:-1);
490 		rg->last = rg->head = bf_elist_prepend(BF_ELIST(rg->head), BF_ELIST(new_change(markstart, TRUE)));
491 	}
492 
493 	oldlast = rg->last;
494 	rg->last = CHANGE(rg->last)->next;
495 	/* oldlast has he position _before_ the deleted area */
496 	if (CHANGE(oldlast)->is_start == FALSE) {
497 		if (oldlast->pos == markstart) {
498 			Tchange *toremove = oldlast;
499 			/* previous region ends at our start, merge the previous and this region together */
500 			oldlast = CHANGE(bf_elist_remove(BF_ELIST(oldlast))); /* returns 'change->prev' if there is one */
501 			g_slice_free(Tchange,toremove);
502 #ifdef PROFILE_MARKREGION
503 			prof.num_allocs--;
504 #endif
505 		} else {
506 			/* previous region ended before our start, start a new region */
507 			oldlast = CHANGE(bf_elist_append(BF_ELIST(oldlast), BF_ELIST(new_change(markstart, TRUE))));
508 		}
509 	}
510 	/*now remove entries in the deleted area */
511 	while(rg->last && CHANGE(rg->last)->pos < comparepos) {
512 		Tchange *toremove = CHANGE(rg->last);
513 		rg->last = CHANGE(rg->last)->next;
514 		bf_elist_remove(BF_ELIST(toremove));
515 /*		DBG_MARKREGION("markregion_delete, remove pos=%u, because it is lower than %d\n",toremove->pos,comparepos);*/
516 		g_slice_free(Tchange, toremove);
517 #ifdef PROFILE_MARKREGION
518 		prof.num_allocs--;
519 #endif
520 
521 	}
522 	/* after the delete loop rg->last points to a position equal or beyond comparepos, or NULL if there was no following position */
523 	if (!rg->last) {
524 		rg->tail = rg->last = CHANGE(bf_elist_append(BF_ELIST(oldlast), BF_ELIST(new_change(markend, FALSE))));
525 		return;
526 	}
527 
528 	if (rg->last && CHANGE(rg->last)->pos == comparepos && CHANGE(rg->last)->is_start == TRUE) {
529 		/* if rg->last equals comparepos and is a start, we can remove it to merge the regions. if it is a start,
530 		there should also be an end, so removing this entry should not delete the tail! */
531 		Tchange *toremove = CHANGE(rg->last);
532 		rg->last = CHANGE(rg->last)->next;
533 #ifdef DEVELOPMENT
534 		g_assert(rg->last);
535 #endif
536 		bf_elist_remove(BF_ELIST(toremove));
537 		DBG_MARKREGION("markregion_delete, remove pos=%u, because it equals %d and starts the next region (merge them)\n",toremove->pos,comparepos);
538 #ifdef PROFILE_MARKREGION
539 		prof.num_allocs--;
540 #endif
541 		g_slice_free(Tchange, toremove);
542 		update_offset(rg->last, offset);
543 		return;
544 	}
545 	/*DBG_MARKREGION("rg->last(%u) is_start=%d, oldlast(%u) is_start=%d\n",CHANGE(rg->last)->pos,CHANGE(rg->last)->is_start,oldlast->pos,oldlast->is_start);*/
546 	if (CHANGE(rg->last)->is_start == TRUE) {
547 		bf_elist_prepend(BF_ELIST(rg->last), BF_ELIST(new_change(markend, FALSE)));
548 	}
549 	update_offset(rg->last, offset);
550 }
551 
552 /*
553 markregion_nochange is called from the scanning engine when the scanning
554 engine detects that the region needs to be enlarged. The code to add these
555 to the Tregions is equal to markregion_delete with an offset of zero.
556  */
557 void
markregion_nochange(Tregions * rg,guint markstart,guint markend)558 markregion_nochange(Tregions *rg, guint markstart, guint markend)
559 {
560 	DBG_MARKREGION("markregion_nochange, markstart=%u, markend=%u\n",markstart,markend);
561 #ifdef DEVELOPMENT
562 	if (markstart == BF_POSITION_UNDEFINED || markend == BF_POSITION_UNDEFINED) {
563 		g_print("ABORT: markregion_nochange is called with a region UNDEFINED, markstart=%d, markend=%d\n",markstart,markend);
564 		g_assert_not_reached();
565 	}
566 #endif
567 	markregion_delete(rg, markstart, markend, 0);
568 }
569 
570 /*
571 markregion_get_region returns the first region in Tregions. Some calling funcions
572 (especially find_region_to_scan) call this function multiple times to find the next
573 region, see if it close and merge them. To support this this function returns a pointer
574 to the current TChange, so if you call it the next time, pass this pointer as
575 second parameter, and you get the next one instead of the first region.
576 */
577 gpointer
markregion_get_region(Tregions * rg,gpointer cur,guint * start,guint * end)578 markregion_get_region(Tregions *rg, gpointer cur, guint *start, guint *end)
579 {
580 #ifdef DEVELOPMENT
581 	markregion_verify_integrity(rg);
582 #endif
583 	DBG_MARKREGION("markregion_get_region, cur->pos(%d), head(%d)|tail(%d)\n",cur?CHANGE(cur)->pos:-1
584 						,rg->head?CHANGE(rg->head)->pos:-1
585 						,rg->tail?CHANGE(rg->tail)->pos:-1);
586 	if (cur == NULL) {
587 		if (rg->head==NULL) {
588 			*start = BF_OFFSET_UNDEFINED;
589 			*end = BF_OFFSET_UNDEFINED;
590 			return NULL;
591 		}
592 		cur = rg->head;
593 	}
594 
595 #ifdef DEVELOPMENT
596 	if (!CHANGE(cur)->is_start) {
597 		g_print("ABORT: get_region is called, and cur is not a start of region\n");
598 		g_assert_not_reached();
599 	}
600 #endif
601 	*start = CHANGE(cur)->pos;
602 	cur = CHANGE(cur)->next;
603 #ifdef DEVELOPMENT
604 	if (!cur) {
605 		g_print("ABORT: get_region is called, and next cur does not exist\n");
606 		g_assert_not_reached();
607 	}
608 	if (CHANGE(cur)->is_start) {
609 		g_print("ABORT: get_region is called, and next cur is a start of region\n");
610 		g_assert_not_reached();
611 	}
612 #endif
613 	*end = CHANGE(cur)->pos;
614 	cur = CHANGE(cur)->next;
615 	DBG_MARKREGION("markregion_get_region, start=%u,end=%u,cur->pos(%d)\n",*start,*end,cur?CHANGE(cur)->pos:-1);
616 	return cur;
617 }
618 
619 #endif
620