1 /* Do not edit: this file is generated from https://git.gnome.org/browse/gtksourceview/plain/gtksourceview/gtksourceregion.c */
2 
3 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
4  * gspellregion.c - GtkTextMark-based region utility
5  * This file is part of GtkSourceView
6  *
7  * Copyright (C) 2002 Gustavo Giráldez <gustavo.giraldez@gmx.net>
8  * Copyright (C) 2016 Sébastien Wilmet <swilmet@gnome.org>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include "gspellregion.h"
30 
31 /*
32  * SECTION:region
33  * @Short_description: Region utility
34  * @Title: GspellRegion
35  * @See_also: #GtkTextBuffer
36  *
37  * A #GspellRegion permits to store a group of subregions of a
38  * #GtkTextBuffer. #GspellRegion stores the subregions with pairs of
39  * #GtkTextMark's, so the region is still valid after insertions and deletions
40  * in the #GtkTextBuffer.
41  *
42  * The #GtkTextMark for the start of a subregion has a left gravity, while the
43  * #GtkTextMark for the end of a subregion has a right gravity.
44  *
45  * The typical use-case of #GspellRegion is to scan a #GtkTextBuffer chunk by
46  * chunk, not the whole buffer at once to not block the user interface. The
47  * #GspellRegion represents in that case the remaining region to scan. You
48  * can listen to the #GtkTextBuffer::insert-text and
49  * #GtkTextBuffer::delete-range signals to update the #GspellRegion
50  * accordingly.
51  *
52  * To iterate through the subregions, you need to use a #GspellRegionIter,
53  * for example:
54  * |[
55  * GspellRegion *region;
56  * GspellRegionIter region_iter;
57  *
58  * _gspell_region_get_start_region_iter (region, &region_iter);
59  *
60  * while (!_gspell_region_iter_is_end (&region_iter))
61  * {
62  *         GtkTextIter subregion_start;
63  *         GtkTextIter subregion_end;
64  *
65  *         if (!_gspell_region_iter_get_subregion (&region_iter,
66  *                                                    &subregion_start,
67  *                                                    &subregion_end))
68  *         {
69  *                 break;
70  *         }
71  *
72  *         // Do something useful with the subregion.
73  *
74  *         _gspell_region_iter_next (&region_iter);
75  * }
76  * ]|
77  */
78 
79 /* With the gravities of the GtkTextMarks, it is possible for subregions to
80  * become interlaced:
81  * Buffer content:
82  *   "hello world"
83  * Add two subregions:
84  *   "[hello] [world]"
85  * Delete the space:
86  *   "[hello][world]"
87  * Undo:
88  *   "[hello[ ]world]"
89  *
90  * FIXME: when iterating through the subregions, it should simplify them first.
91  * I don't know if it's done (swilmet).
92  */
93 
94 #undef ENABLE_DEBUG
95 /*
96 #define ENABLE_DEBUG
97 */
98 
99 #ifdef ENABLE_DEBUG
100 #define DEBUG(x) (x)
101 #else
102 #define DEBUG(x)
103 #endif
104 
105 typedef struct _GspellRegionPrivate GspellRegionPrivate;
106 typedef struct _Subregion Subregion;
107 typedef struct _GspellRegionIterReal GspellRegionIterReal;
108 
109 struct _GspellRegionPrivate
110 {
111 	/* Weak pointer to the buffer. */
112 	GtkTextBuffer *buffer;
113 
114 	/* List of sorted 'Subregion*' */
115 	GList *subregions;
116 
117 	guint32 timestamp;
118 };
119 
120 struct _Subregion
121 {
122 	GtkTextMark *start;
123 	GtkTextMark *end;
124 };
125 
126 struct _GspellRegionIterReal
127 {
128 	GspellRegion *region;
129 	guint32 region_timestamp;
130 	GList *subregions;
131 };
132 
133 enum
134 {
135 	PROP_0,
136 	PROP_BUFFER,
137 	N_PROPERTIES
138 };
139 
140 static GParamSpec *properties[N_PROPERTIES];
141 
G_DEFINE_TYPE_WITH_PRIVATE(GspellRegion,_gspell_region,G_TYPE_OBJECT)142 G_DEFINE_TYPE_WITH_PRIVATE (GspellRegion, _gspell_region, G_TYPE_OBJECT)
143 
144 #ifdef ENABLE_DEBUG
145 static void
146 print_region (GspellRegion *region)
147 {
148 	gchar *str;
149 
150 	str = _gspell_region_to_string (region);
151 	g_print ("%s\n", str);
152 	g_free (str);
153 }
154 #endif
155 
156 /* Find and return a subregion node which contains the given text
157  * iter.  If left_side is TRUE, return the subregion which contains
158  * the text iter or which is the leftmost; else return the rightmost
159  * subregion.
160  */
161 static GList *
find_nearest_subregion(GspellRegion * region,const GtkTextIter * iter,GList * begin,gboolean leftmost,gboolean include_edges)162 find_nearest_subregion (GspellRegion   *region,
163 			const GtkTextIter *iter,
164 			GList             *begin,
165 			gboolean           leftmost,
166 			gboolean           include_edges)
167 {
168 	GspellRegionPrivate *priv = _gspell_region_get_instance_private (region);
169 	GList *retval;
170 	GList *l;
171 
172 	g_assert (iter != NULL);
173 
174 	if (begin == NULL)
175 	{
176 		begin = priv->subregions;
177 	}
178 
179 	if (begin != NULL)
180 	{
181 		retval = begin->prev;
182 	}
183 	else
184 	{
185 		retval = NULL;
186 	}
187 
188 	for (l = begin; l != NULL; l = l->next)
189 	{
190 		GtkTextIter sr_iter;
191 		Subregion *sr = l->data;
192 		gint cmp;
193 
194 		if (!leftmost)
195 		{
196 			gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_iter, sr->end);
197 			cmp = gtk_text_iter_compare (iter, &sr_iter);
198 			if (cmp < 0 || (cmp == 0 && include_edges))
199 			{
200 				retval = l;
201 				break;
202 			}
203 
204 		}
205 		else
206 		{
207 			gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_iter, sr->start);
208 			cmp = gtk_text_iter_compare (iter, &sr_iter);
209 			if (cmp > 0 || (cmp == 0 && include_edges))
210 			{
211 				retval = l;
212 			}
213 			else
214 			{
215 				break;
216 			}
217 		}
218 	}
219 
220 	return retval;
221 }
222 
223 static void
_gspell_region_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)224 _gspell_region_get_property (GObject    *object,
225 				guint       prop_id,
226 				GValue     *value,
227 				GParamSpec *pspec)
228 {
229 	GspellRegion *region = GSPELL_REGION (object);
230 
231 	switch (prop_id)
232 	{
233 		case PROP_BUFFER:
234 			g_value_set_object (value, _gspell_region_get_buffer (region));
235 			break;
236 
237 		default:
238 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
239 			break;
240 	}
241 }
242 
243 static void
_gspell_region_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)244 _gspell_region_set_property (GObject      *object,
245 				guint         prop_id,
246 				const GValue *value,
247 				GParamSpec   *pspec)
248 {
249 	GspellRegionPrivate *priv = _gspell_region_get_instance_private (GSPELL_REGION (object));
250 
251 	switch (prop_id)
252 	{
253 		case PROP_BUFFER:
254 			g_assert (priv->buffer == NULL);
255 			priv->buffer = g_value_get_object (value);
256 			g_object_add_weak_pointer (G_OBJECT (priv->buffer),
257 						   (gpointer *) &priv->buffer);
258 			break;
259 
260 		default:
261 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
262 			break;
263 	}
264 }
265 
266 static void
_gspell_region_dispose(GObject * object)267 _gspell_region_dispose (GObject *object)
268 {
269 	GspellRegionPrivate *priv = _gspell_region_get_instance_private (GSPELL_REGION (object));
270 
271 	while (priv->subregions != NULL)
272 	{
273 		Subregion *sr = priv->subregions->data;
274 
275 		if (priv->buffer != NULL)
276 		{
277 			gtk_text_buffer_delete_mark (priv->buffer, sr->start);
278 			gtk_text_buffer_delete_mark (priv->buffer, sr->end);
279 		}
280 
281 		g_slice_free (Subregion, sr);
282 		priv->subregions = g_list_delete_link (priv->subregions, priv->subregions);
283 	}
284 
285 	if (priv->buffer != NULL)
286 	{
287 		g_object_remove_weak_pointer (G_OBJECT (priv->buffer),
288 					      (gpointer *) &priv->buffer);
289 
290 		priv->buffer = NULL;
291 	}
292 
293 	G_OBJECT_CLASS (_gspell_region_parent_class)->dispose (object);
294 }
295 
296 static void
_gspell_region_class_init(GspellRegionClass * klass)297 _gspell_region_class_init (GspellRegionClass *klass)
298 {
299 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
300 
301 	object_class->get_property = _gspell_region_get_property;
302 	object_class->set_property = _gspell_region_set_property;
303 	object_class->dispose = _gspell_region_dispose;
304 
305 	/*
306 	 * GspellRegion:buffer:
307 	 *
308 	 * The #GtkTextBuffer. The #GspellRegion has a weak reference to the
309 	 * buffer.
310 	 *
311 	 * Since: 3.22
312 	 */
313 	properties[PROP_BUFFER] =
314 		g_param_spec_object ("buffer",
315 				     "Buffer",
316 				     "",
317 				     GTK_TYPE_TEXT_BUFFER,
318 				     G_PARAM_READWRITE |
319 				     G_PARAM_CONSTRUCT_ONLY |
320 				     G_PARAM_STATIC_STRINGS);
321 
322 	g_object_class_install_properties (object_class, N_PROPERTIES, properties);
323 }
324 
325 static void
_gspell_region_init(GspellRegion * region)326 _gspell_region_init (GspellRegion *region)
327 {
328 }
329 
330 /*
331  * _gspell_region_new:
332  * @buffer: a #GtkTextBuffer.
333  *
334  * Returns: a new #GspellRegion object for @buffer.
335  * Since: 3.22
336  */
337 GspellRegion *
_gspell_region_new(GtkTextBuffer * buffer)338 _gspell_region_new (GtkTextBuffer *buffer)
339 {
340 	g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
341 
342 	return g_object_new (GSPELL_TYPE_REGION,
343 			     "buffer", buffer,
344 			     NULL);
345 }
346 
347 /*
348  * _gspell_region_get_buffer:
349  * @region: a #GspellRegion.
350  *
351  * Returns: (transfer none) (nullable): the #GtkTextBuffer.
352  * Since: 3.22
353  */
354 GtkTextBuffer *
_gspell_region_get_buffer(GspellRegion * region)355 _gspell_region_get_buffer (GspellRegion *region)
356 {
357 	GspellRegionPrivate *priv;
358 
359 	g_return_val_if_fail (GSPELL_IS_REGION (region), NULL);
360 
361 	priv = _gspell_region_get_instance_private (region);
362 	return priv->buffer;
363 }
364 
365 static void
_gspell_region_clear_zero_length_subregions(GspellRegion * region)366 _gspell_region_clear_zero_length_subregions (GspellRegion *region)
367 {
368 	GspellRegionPrivate *priv = _gspell_region_get_instance_private (region);
369 	GList *node;
370 
371 	node = priv->subregions;
372 	while (node != NULL)
373 	{
374 		Subregion *sr = node->data;
375 		GtkTextIter start;
376 		GtkTextIter end;
377 
378 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &start, sr->start);
379 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &end, sr->end);
380 
381 		if (gtk_text_iter_equal (&start, &end))
382 		{
383 			gtk_text_buffer_delete_mark (priv->buffer, sr->start);
384 			gtk_text_buffer_delete_mark (priv->buffer, sr->end);
385 			g_slice_free (Subregion, sr);
386 
387 			if (node == priv->subregions)
388 			{
389 				priv->subregions = node = g_list_delete_link (node, node);
390 			}
391 			else
392 			{
393 				node = g_list_delete_link (node, node);
394 			}
395 
396 			priv->timestamp++;
397 		}
398 		else
399 		{
400 			node = node->next;
401 		}
402 	}
403 }
404 
405 /*
406  * _gspell_region_add_subregion:
407  * @region: a #GspellRegion.
408  * @_start: the start of the subregion.
409  * @_end: the end of the subregion.
410  *
411  * Adds the subregion delimited by @_start and @_end to @region.
412  *
413  * Since: 3.22
414  */
415 void
_gspell_region_add_subregion(GspellRegion * region,const GtkTextIter * _start,const GtkTextIter * _end)416 _gspell_region_add_subregion (GspellRegion   *region,
417 				 const GtkTextIter *_start,
418 				 const GtkTextIter *_end)
419 {
420 	GspellRegionPrivate *priv;
421 	GList *start_node;
422 	GList *end_node;
423 	GtkTextIter start;
424 	GtkTextIter end;
425 
426 	g_return_if_fail (GSPELL_IS_REGION (region));
427 	g_return_if_fail (_start != NULL);
428 	g_return_if_fail (_end != NULL);
429 
430 	priv = _gspell_region_get_instance_private (region);
431 
432 	if (priv->buffer == NULL)
433 	{
434 		return;
435 	}
436 
437 	start = *_start;
438 	end = *_end;
439 
440 	DEBUG (g_print ("---\n"));
441 	DEBUG (print_region (region));
442 	DEBUG (g_message ("region_add (%d, %d)",
443 			  gtk_text_iter_get_offset (&start),
444 			  gtk_text_iter_get_offset (&end)));
445 
446 	gtk_text_iter_order (&start, &end);
447 
448 	/* Don't add zero-length regions. */
449 	if (gtk_text_iter_equal (&start, &end))
450 	{
451 		return;
452 	}
453 
454 	/* Find bounding subregions. */
455 	start_node = find_nearest_subregion (region, &start, NULL, FALSE, TRUE);
456 	end_node = find_nearest_subregion (region, &end, start_node, TRUE, TRUE);
457 
458 	if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
459 	{
460 		/* Create the new subregion. */
461 		Subregion *sr = g_slice_new0 (Subregion);
462 		sr->start = gtk_text_buffer_create_mark (priv->buffer, NULL, &start, TRUE);
463 		sr->end = gtk_text_buffer_create_mark (priv->buffer, NULL, &end, FALSE);
464 
465 		if (start_node == NULL)
466 		{
467 			/* Append the new region. */
468 			priv->subregions = g_list_append (priv->subregions, sr);
469 		}
470 		else if (end_node == NULL)
471 		{
472 			/* Prepend the new region. */
473 			priv->subregions = g_list_prepend (priv->subregions, sr);
474 		}
475 		else
476 		{
477 			/* We are in the middle of two subregions. */
478 			priv->subregions = g_list_insert_before (priv->subregions, start_node, sr);
479 		}
480 	}
481 	else
482 	{
483 		GtkTextIter iter;
484 		Subregion *sr = start_node->data;
485 
486 		if (start_node != end_node)
487 		{
488 			/* We need to merge some subregions. */
489 			GList *l = start_node->next;
490 			Subregion *q;
491 
492 			gtk_text_buffer_delete_mark (priv->buffer, sr->end);
493 
494 			while (l != end_node)
495 			{
496 				q = l->data;
497 				gtk_text_buffer_delete_mark (priv->buffer, q->start);
498 				gtk_text_buffer_delete_mark (priv->buffer, q->end);
499 				g_slice_free (Subregion, q);
500 				l = g_list_delete_link (l, l);
501 			}
502 
503 			q = l->data;
504 			gtk_text_buffer_delete_mark (priv->buffer, q->start);
505 			sr->end = q->end;
506 			g_slice_free (Subregion, q);
507 			l = g_list_delete_link (l, l);
508 		}
509 
510 		/* Now move marks if that action expands the region. */
511 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, sr->start);
512 		if (gtk_text_iter_compare (&iter, &start) > 0)
513 		{
514 			gtk_text_buffer_move_mark (priv->buffer, sr->start, &start);
515 		}
516 
517 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &iter, sr->end);
518 		if (gtk_text_iter_compare (&iter, &end) < 0)
519 		{
520 			gtk_text_buffer_move_mark (priv->buffer, sr->end, &end);
521 		}
522 	}
523 
524 	priv->timestamp++;
525 
526 	DEBUG (print_region (region));
527 }
528 
529 /*
530  * _gspell_region_add_region:
531  * @region: a #GspellRegion.
532  * @region_to_add: (nullable): the #GspellRegion to add to @region, or %NULL.
533  *
534  * Adds @region_to_add to @region. @region_to_add is not modified.
535  *
536  * Since: 3.22
537  */
538 void
_gspell_region_add_region(GspellRegion * region,GspellRegion * region_to_add)539 _gspell_region_add_region (GspellRegion *region,
540 			      GspellRegion *region_to_add)
541 {
542 	GspellRegionIter iter;
543 	GtkTextBuffer *region_buffer;
544 	GtkTextBuffer *region_to_add_buffer;
545 
546 	g_return_if_fail (GSPELL_IS_REGION (region));
547 	g_return_if_fail (region_to_add == NULL || GSPELL_IS_REGION (region_to_add));
548 
549 	if (region_to_add == NULL)
550 	{
551 		return;
552 	}
553 
554 	region_buffer = _gspell_region_get_buffer (region);
555 	region_to_add_buffer = _gspell_region_get_buffer (region_to_add);
556 	g_return_if_fail (region_buffer == region_to_add_buffer);
557 
558 	if (region_buffer == NULL)
559 	{
560 		return;
561 	}
562 
563 	_gspell_region_get_start_region_iter (region_to_add, &iter);
564 
565 	while (!_gspell_region_iter_is_end (&iter))
566 	{
567 		GtkTextIter subregion_start;
568 		GtkTextIter subregion_end;
569 
570 		if (!_gspell_region_iter_get_subregion (&iter,
571 							   &subregion_start,
572 							   &subregion_end))
573 		{
574 			break;
575 		}
576 
577 		_gspell_region_add_subregion (region,
578 						 &subregion_start,
579 						 &subregion_end);
580 
581 		_gspell_region_iter_next (&iter);
582 	}
583 }
584 
585 /*
586  * _gspell_region_subtract_subregion:
587  * @region: a #GspellRegion.
588  * @_start: the start of the subregion.
589  * @_end: the end of the subregion.
590  *
591  * Subtracts the subregion delimited by @_start and @_end from @region.
592  *
593  * Since: 3.22
594  */
595 void
_gspell_region_subtract_subregion(GspellRegion * region,const GtkTextIter * _start,const GtkTextIter * _end)596 _gspell_region_subtract_subregion (GspellRegion   *region,
597 				      const GtkTextIter *_start,
598 				      const GtkTextIter *_end)
599 {
600 	GspellRegionPrivate *priv;
601 	GList *start_node;
602 	GList *end_node;
603 	GList *node;
604 	GtkTextIter sr_start_iter;
605 	GtkTextIter sr_end_iter;
606 	gboolean done;
607 	gboolean start_is_outside;
608 	gboolean end_is_outside;
609 	Subregion *sr;
610 	GtkTextIter start;
611 	GtkTextIter end;
612 
613 	g_return_if_fail (GSPELL_IS_REGION (region));
614 	g_return_if_fail (_start != NULL);
615 	g_return_if_fail (_end != NULL);
616 
617 	priv = _gspell_region_get_instance_private (region);
618 
619 	if (priv->buffer == NULL)
620 	{
621 		return;
622 	}
623 
624 	start = *_start;
625 	end = *_end;
626 
627 	DEBUG (g_print ("---\n"));
628 	DEBUG (print_region (region));
629 	DEBUG (g_message ("region_substract (%d, %d)",
630 			  gtk_text_iter_get_offset (&start),
631 			  gtk_text_iter_get_offset (&end)));
632 
633 	gtk_text_iter_order (&start, &end);
634 
635 	/* Find bounding subregions. */
636 	start_node = find_nearest_subregion (region, &start, NULL, FALSE, FALSE);
637 	end_node = find_nearest_subregion (region, &end, start_node, TRUE, FALSE);
638 
639 	/* Easy case first. */
640 	if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
641 	{
642 		return;
643 	}
644 
645 	/* Deal with the start point. */
646 	start_is_outside = end_is_outside = FALSE;
647 
648 	sr = start_node->data;
649 	gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
650 	gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
651 
652 	if (gtk_text_iter_in_range (&start, &sr_start_iter, &sr_end_iter) &&
653 	    !gtk_text_iter_equal (&start, &sr_start_iter))
654 	{
655 		/* The starting point is inside the first subregion. */
656 		if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter) &&
657 		    !gtk_text_iter_equal (&end, &sr_end_iter))
658 		{
659 			/* The ending point is also inside the first
660 			 * subregion: we need to split.
661 			 */
662 			Subregion *new_sr = g_slice_new0 (Subregion);
663 			new_sr->end = sr->end;
664 			new_sr->start = gtk_text_buffer_create_mark (priv->buffer,
665 								     NULL,
666 								     &end,
667 								     TRUE);
668 
669 			start_node = g_list_insert_before (start_node, start_node->next, new_sr);
670 
671 			sr->end = gtk_text_buffer_create_mark (priv->buffer,
672 							       NULL,
673 							       &start,
674 							       FALSE);
675 
676 			/* No further processing needed. */
677 			DEBUG (g_message ("subregion splitted"));
678 
679 			return;
680 		}
681 		else
682 		{
683 			/* The ending point is outside, so just move
684 			 * the end of the subregion to the starting point.
685 			 */
686 			gtk_text_buffer_move_mark (priv->buffer, sr->end, &start);
687 		}
688 	}
689 	else
690 	{
691 		/* The starting point is outside (and so to the left)
692 		 * of the first subregion.
693 		 */
694 		DEBUG (g_message ("start is outside"));
695 
696 		start_is_outside = TRUE;
697 	}
698 
699 	/* Deal with the end point. */
700 	if (start_node != end_node)
701 	{
702 		sr = end_node->data;
703 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
704 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
705 	}
706 
707 	if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter) &&
708 	    !gtk_text_iter_equal (&end, &sr_end_iter))
709 	{
710 		/* Ending point is inside, move the start mark. */
711 		gtk_text_buffer_move_mark (priv->buffer, sr->start, &end);
712 	}
713 	else
714 	{
715 		end_is_outside = TRUE;
716 		DEBUG (g_message ("end is outside"));
717 	}
718 
719 	/* Finally remove any intermediate subregions. */
720 	done = FALSE;
721 	node = start_node;
722 
723 	while (!done)
724 	{
725 		if (node == end_node)
726 		{
727 			/* We are done, exit in the next iteration. */
728 			done = TRUE;
729 		}
730 
731 		if ((node == start_node && !start_is_outside) ||
732 		    (node == end_node && !end_is_outside))
733 		{
734 			/* Skip starting or ending node. */
735 			node = node->next;
736 		}
737 		else
738 		{
739 			GList *l = node->next;
740 			sr = node->data;
741 			gtk_text_buffer_delete_mark (priv->buffer, sr->start);
742 			gtk_text_buffer_delete_mark (priv->buffer, sr->end);
743 			g_slice_free (Subregion, sr);
744 			priv->subregions = g_list_delete_link (priv->subregions, node);
745 			node = l;
746 		}
747 	}
748 
749 	priv->timestamp++;
750 
751 	DEBUG (print_region (region));
752 
753 	/* Now get rid of empty subregions. */
754 	_gspell_region_clear_zero_length_subregions (region);
755 
756 	DEBUG (print_region (region));
757 }
758 
759 /*
760  * _gspell_region_subtract_region:
761  * @region: a #GspellRegion.
762  * @region_to_subtract: (nullable): the #GspellRegion to subtract from
763  *   @region, or %NULL.
764  *
765  * Subtracts @region_to_subtract from @region. @region_to_subtract is not
766  * modified.
767  *
768  * Since: 3.22
769  */
770 void
_gspell_region_subtract_region(GspellRegion * region,GspellRegion * region_to_subtract)771 _gspell_region_subtract_region (GspellRegion *region,
772 				   GspellRegion *region_to_subtract)
773 {
774 	GtkTextBuffer *region_buffer;
775 	GtkTextBuffer *region_to_subtract_buffer;
776 	GspellRegionIter iter;
777 
778 	g_return_if_fail (GSPELL_IS_REGION (region));
779 	g_return_if_fail (region_to_subtract == NULL || GSPELL_IS_REGION (region_to_subtract));
780 
781 	region_buffer = _gspell_region_get_buffer (region);
782 	region_to_subtract_buffer = _gspell_region_get_buffer (region_to_subtract);
783 	g_return_if_fail (region_buffer == region_to_subtract_buffer);
784 
785 	if (region_buffer == NULL)
786 	{
787 		return;
788 	}
789 
790 	_gspell_region_get_start_region_iter (region_to_subtract, &iter);
791 
792 	while (!_gspell_region_iter_is_end (&iter))
793 	{
794 		GtkTextIter subregion_start;
795 		GtkTextIter subregion_end;
796 
797 		if (!_gspell_region_iter_get_subregion (&iter,
798 							   &subregion_start,
799 							   &subregion_end))
800 		{
801 			break;
802 		}
803 
804 		_gspell_region_subtract_subregion (region,
805 						      &subregion_start,
806 						      &subregion_end);
807 
808 		_gspell_region_iter_next (&iter);
809 	}
810 }
811 
812 /*
813  * _gspell_region_is_empty:
814  * @region: (nullable): a #GspellRegion, or %NULL.
815  *
816  * Returns whether the @region is empty. A %NULL @region is considered empty.
817  *
818  * Returns: whether the @region is empty.
819  * Since: 3.22
820  */
821 gboolean
_gspell_region_is_empty(GspellRegion * region)822 _gspell_region_is_empty (GspellRegion *region)
823 {
824 	GspellRegionIter region_iter;
825 
826 	if (region == NULL)
827 	{
828 		return TRUE;
829 	}
830 
831 	/* A #GspellRegion can contain empty subregions. So checking the
832 	 * number of subregions is not sufficient.
833 	 * When calling _gspell_region_add_subregion() with equal iters, the
834 	 * subregion is not added. But when a subregion becomes empty, due to
835 	 * text deletion, the subregion is not removed from the
836 	 * #GspellRegion.
837 	 */
838 
839 	_gspell_region_get_start_region_iter (region, &region_iter);
840 
841 	while (!_gspell_region_iter_is_end (&region_iter))
842 	{
843 		GtkTextIter subregion_start;
844 		GtkTextIter subregion_end;
845 
846 		if (!_gspell_region_iter_get_subregion (&region_iter,
847 							   &subregion_start,
848 							   &subregion_end))
849 		{
850 			return TRUE;
851 		}
852 
853 		if (!gtk_text_iter_equal (&subregion_start, &subregion_end))
854 		{
855 			return FALSE;
856 		}
857 
858 		_gspell_region_iter_next (&region_iter);
859 	}
860 
861 	return TRUE;
862 }
863 
864 /*
865  * _gspell_region_get_bounds:
866  * @region: a #GspellRegion.
867  * @start: (out) (optional): iterator to initialize with the start of @region,
868  *   or %NULL.
869  * @end: (out) (optional): iterator to initialize with the end of @region,
870  *   or %NULL.
871  *
872  * Gets the @start and @end bounds of the @region.
873  *
874  * Returns: %TRUE if @start and @end have been set successfully (if non-%NULL),
875  *   or %FALSE if the @region is empty.
876  * Since: 3.22
877  */
878 gboolean
_gspell_region_get_bounds(GspellRegion * region,GtkTextIter * start,GtkTextIter * end)879 _gspell_region_get_bounds (GspellRegion *region,
880 			      GtkTextIter     *start,
881 			      GtkTextIter     *end)
882 {
883 	GspellRegionPrivate *priv;
884 
885 	g_return_val_if_fail (GSPELL_IS_REGION (region), FALSE);
886 
887 	priv = _gspell_region_get_instance_private (region);
888 
889 	if (priv->buffer == NULL ||
890 	    _gspell_region_is_empty (region))
891 	{
892 		return FALSE;
893 	}
894 
895 	g_assert (priv->subregions != NULL);
896 
897 	if (start != NULL)
898 	{
899 		Subregion *first_subregion = priv->subregions->data;
900 		gtk_text_buffer_get_iter_at_mark (priv->buffer, start, first_subregion->start);
901 	}
902 
903 	if (end != NULL)
904 	{
905 		Subregion *last_subregion = g_list_last (priv->subregions)->data;
906 		gtk_text_buffer_get_iter_at_mark (priv->buffer, end, last_subregion->end);
907 	}
908 
909 	return TRUE;
910 }
911 
912 /*
913  * _gspell_region_intersect_subregion:
914  * @region: a #GspellRegion.
915  * @_start: the start of the subregion.
916  * @_end: the end of the subregion.
917  *
918  * Returns the intersection between @region and the subregion delimited by
919  * @_start and @_end. @region is not modified.
920  *
921  * Returns: (transfer full) (nullable): the intersection as a new
922  *   #GspellRegion.
923  * Since: 3.22
924  */
925 GspellRegion *
_gspell_region_intersect_subregion(GspellRegion * region,const GtkTextIter * _start,const GtkTextIter * _end)926 _gspell_region_intersect_subregion (GspellRegion   *region,
927 				       const GtkTextIter *_start,
928 				       const GtkTextIter *_end)
929 {
930 	GspellRegionPrivate *priv;
931 	GspellRegion *new_region;
932 	GspellRegionPrivate *new_priv;
933 	GList *start_node;
934 	GList *end_node;
935 	GList *node;
936 	GtkTextIter sr_start_iter;
937 	GtkTextIter sr_end_iter;
938 	Subregion *sr;
939 	Subregion *new_sr;
940 	gboolean done;
941 	GtkTextIter start;
942 	GtkTextIter end;
943 
944 	g_return_val_if_fail (GSPELL_IS_REGION (region), NULL);
945 	g_return_val_if_fail (_start != NULL, NULL);
946 	g_return_val_if_fail (_end != NULL, NULL);
947 
948 	priv = _gspell_region_get_instance_private (region);
949 
950 	if (priv->buffer == NULL)
951 	{
952 		return NULL;
953 	}
954 
955 	start = *_start;
956 	end = *_end;
957 
958 	gtk_text_iter_order (&start, &end);
959 
960 	/* Find bounding subregions. */
961 	start_node = find_nearest_subregion (region, &start, NULL, FALSE, FALSE);
962 	end_node = find_nearest_subregion (region, &end, start_node, TRUE, FALSE);
963 
964 	/* Easy case first. */
965 	if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
966 	{
967 		return NULL;
968 	}
969 
970 	new_region = _gspell_region_new (priv->buffer);
971 	new_priv = _gspell_region_get_instance_private (new_region);
972 	done = FALSE;
973 
974 	sr = start_node->data;
975 	gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
976 	gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
977 
978 	/* Starting node. */
979 	if (gtk_text_iter_in_range (&start, &sr_start_iter, &sr_end_iter))
980 	{
981 		new_sr = g_slice_new0 (Subregion);
982 		new_priv->subregions = g_list_prepend (new_priv->subregions, new_sr);
983 
984 		new_sr->start = gtk_text_buffer_create_mark (new_priv->buffer,
985 							     NULL,
986 							     &start,
987 							     TRUE);
988 
989 		if (start_node == end_node)
990 		{
991 			/* Things will finish shortly. */
992 			done = TRUE;
993 			if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter))
994 			{
995 				new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
996 									   NULL,
997 									   &end,
998 									   FALSE);
999 			}
1000 			else
1001 			{
1002 				new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1003 									   NULL,
1004 									   &sr_end_iter,
1005 									   FALSE);
1006 			}
1007 		}
1008 		else
1009 		{
1010 			new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1011 								   NULL,
1012 								   &sr_end_iter,
1013 								   FALSE);
1014 		}
1015 
1016 		node = start_node->next;
1017 	}
1018 	else
1019 	{
1020 		/* start should be the same as the subregion, so copy it in the
1021 		 * loop.
1022 		 */
1023 		node = start_node;
1024 	}
1025 
1026 	if (!done)
1027 	{
1028 		while (node != end_node)
1029 		{
1030 			/* Copy intermediate subregions verbatim. */
1031 			sr = node->data;
1032 			gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
1033 			gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
1034 
1035 			new_sr = g_slice_new0 (Subregion);
1036 			new_priv->subregions = g_list_prepend (new_priv->subregions, new_sr);
1037 
1038 			new_sr->start = gtk_text_buffer_create_mark (new_priv->buffer,
1039 								     NULL,
1040 								     &sr_start_iter,
1041 								     TRUE);
1042 
1043 			new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1044 								   NULL,
1045 								   &sr_end_iter,
1046 								   FALSE);
1047 
1048 			/* Next node. */
1049 			node = node->next;
1050 		}
1051 
1052 		/* Ending node. */
1053 		sr = node->data;
1054 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_start_iter, sr->start);
1055 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &sr_end_iter, sr->end);
1056 
1057 		new_sr = g_slice_new0 (Subregion);
1058 		new_priv->subregions = g_list_prepend (new_priv->subregions, new_sr);
1059 
1060 		new_sr->start = gtk_text_buffer_create_mark (new_priv->buffer,
1061 							     NULL,
1062 							     &sr_start_iter,
1063 							     TRUE);
1064 
1065 		if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter))
1066 		{
1067 			new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1068 								   NULL,
1069 								   &end,
1070 								   FALSE);
1071 		}
1072 		else
1073 		{
1074 			new_sr->end = gtk_text_buffer_create_mark (new_priv->buffer,
1075 								   NULL,
1076 								   &sr_end_iter,
1077 								   FALSE);
1078 		}
1079 	}
1080 
1081 	new_priv->subregions = g_list_reverse (new_priv->subregions);
1082 	return new_region;
1083 }
1084 
1085 /*
1086  * _gspell_region_intersect_region:
1087  * @region1: (nullable): a #GspellRegion, or %NULL.
1088  * @region2: (nullable): a #GspellRegion, or %NULL.
1089  *
1090  * Returns the intersection between @region1 and @region2. @region1 and
1091  * @region2 are not modified.
1092  *
1093  * Returns: (transfer full) (nullable): the intersection as a #GspellRegion
1094  *   object.
1095  * Since: 3.22
1096  */
1097 GspellRegion *
_gspell_region_intersect_region(GspellRegion * region1,GspellRegion * region2)1098 _gspell_region_intersect_region (GspellRegion *region1,
1099 				    GspellRegion *region2)
1100 {
1101 	GtkTextBuffer *region1_buffer;
1102 	GtkTextBuffer *region2_buffer;
1103 	GspellRegion *full_intersect = NULL;
1104 	GspellRegionIter region2_iter;
1105 
1106 	g_return_val_if_fail (region1 == NULL || GSPELL_IS_REGION (region1), NULL);
1107 	g_return_val_if_fail (region2 == NULL || GSPELL_IS_REGION (region2), NULL);
1108 
1109 	if (region1 == NULL && region2 == NULL)
1110 	{
1111 		return NULL;
1112 	}
1113 	if (region1 == NULL)
1114 	{
1115 		return g_object_ref (region2);
1116 	}
1117 	if (region2 == NULL)
1118 	{
1119 		return g_object_ref (region1);
1120 	}
1121 
1122 	region1_buffer = _gspell_region_get_buffer (region1);
1123 	region2_buffer = _gspell_region_get_buffer (region2);
1124 	g_return_val_if_fail (region1_buffer == region2_buffer, NULL);
1125 
1126 	if (region1_buffer == NULL)
1127 	{
1128 		return NULL;
1129 	}
1130 
1131 	_gspell_region_get_start_region_iter (region2, &region2_iter);
1132 
1133 	while (!_gspell_region_iter_is_end (&region2_iter))
1134 	{
1135 		GtkTextIter subregion2_start;
1136 		GtkTextIter subregion2_end;
1137 		GspellRegion *sub_intersect;
1138 
1139 		if (!_gspell_region_iter_get_subregion (&region2_iter,
1140 							   &subregion2_start,
1141 							   &subregion2_end))
1142 		{
1143 			break;
1144 		}
1145 
1146 		sub_intersect = _gspell_region_intersect_subregion (region1,
1147 								       &subregion2_start,
1148 								       &subregion2_end);
1149 
1150 		if (full_intersect == NULL)
1151 		{
1152 			full_intersect = sub_intersect;
1153 		}
1154 		else
1155 		{
1156 			_gspell_region_add_region (full_intersect, sub_intersect);
1157 			g_clear_object (&sub_intersect);
1158 		}
1159 
1160 		_gspell_region_iter_next (&region2_iter);
1161 	}
1162 
1163 	return full_intersect;
1164 }
1165 
1166 static gboolean
check_iterator(GspellRegionIterReal * real)1167 check_iterator (GspellRegionIterReal *real)
1168 {
1169 	GspellRegionPrivate *priv;
1170 
1171 	if (real->region == NULL)
1172 	{
1173 		goto invalid;
1174 	}
1175 
1176 	priv = _gspell_region_get_instance_private (real->region);
1177 
1178 	if (real->region_timestamp == priv->timestamp)
1179 	{
1180 		return TRUE;
1181 	}
1182 
1183 invalid:
1184 	g_warning ("Invalid GspellRegionIter: either the iterator is "
1185 		   "uninitialized, or the region has been modified since the "
1186 		   "iterator was created.");
1187 
1188 	return FALSE;
1189 }
1190 
1191 /*
1192  * _gspell_region_get_start_region_iter:
1193  * @region: a #GspellRegion.
1194  * @iter: (out): iterator to initialize to the first subregion.
1195  *
1196  * Initializes a #GspellRegionIter to the first subregion of @region. If
1197  * @region is empty, @iter will be initialized to the end iterator.
1198  *
1199  * Since: 3.22
1200  */
1201 void
_gspell_region_get_start_region_iter(GspellRegion * region,GspellRegionIter * iter)1202 _gspell_region_get_start_region_iter (GspellRegion     *region,
1203 					 GspellRegionIter *iter)
1204 {
1205 	GspellRegionPrivate *priv;
1206 	GspellRegionIterReal *real;
1207 
1208 	g_return_if_fail (GSPELL_IS_REGION (region));
1209 	g_return_if_fail (iter != NULL);
1210 
1211 	priv = _gspell_region_get_instance_private (region);
1212 	real = (GspellRegionIterReal *)iter;
1213 
1214 	/* priv->subregions may be NULL, -> end iter */
1215 
1216 	real->region = region;
1217 	real->subregions = priv->subregions;
1218 	real->region_timestamp = priv->timestamp;
1219 }
1220 
1221 /*
1222  * _gspell_region_iter_is_end:
1223  * @iter: a #GspellRegionIter.
1224  *
1225  * Returns: whether @iter is the end iterator.
1226  * Since: 3.22
1227  */
1228 gboolean
_gspell_region_iter_is_end(GspellRegionIter * iter)1229 _gspell_region_iter_is_end (GspellRegionIter *iter)
1230 {
1231 	GspellRegionIterReal *real;
1232 
1233 	g_return_val_if_fail (iter != NULL, FALSE);
1234 
1235 	real = (GspellRegionIterReal *)iter;
1236 	g_return_val_if_fail (check_iterator (real), FALSE);
1237 
1238 	return real->subregions == NULL;
1239 }
1240 
1241 /*
1242  * _gspell_region_iter_next:
1243  * @iter: a #GspellRegionIter.
1244  *
1245  * Moves @iter to the next subregion.
1246  *
1247  * Returns: %TRUE if @iter moved and is dereferenceable, or %FALSE if @iter has
1248  *   been set to the end iterator.
1249  * Since: 3.22
1250  */
1251 gboolean
_gspell_region_iter_next(GspellRegionIter * iter)1252 _gspell_region_iter_next (GspellRegionIter *iter)
1253 {
1254 	GspellRegionIterReal *real;
1255 
1256 	g_return_val_if_fail (iter != NULL, FALSE);
1257 
1258 	real = (GspellRegionIterReal *)iter;
1259 	g_return_val_if_fail (check_iterator (real), FALSE);
1260 
1261 	if (real->subregions != NULL)
1262 	{
1263 		real->subregions = real->subregions->next;
1264 		return TRUE;
1265 	}
1266 
1267 	return FALSE;
1268 }
1269 
1270 /*
1271  * _gspell_region_iter_get_subregion:
1272  * @iter: a #GspellRegionIter.
1273  * @start: (out) (optional): iterator to initialize with the subregion start, or %NULL.
1274  * @end: (out) (optional): iterator to initialize with the subregion end, or %NULL.
1275  *
1276  * Gets the subregion at this iterator.
1277  *
1278  * Returns: %TRUE if @start and @end have been set successfully (if non-%NULL),
1279  *   or %FALSE if @iter is the end iterator or if the region is empty.
1280  * Since: 3.22
1281  */
1282 gboolean
_gspell_region_iter_get_subregion(GspellRegionIter * iter,GtkTextIter * start,GtkTextIter * end)1283 _gspell_region_iter_get_subregion (GspellRegionIter *iter,
1284 				      GtkTextIter         *start,
1285 				      GtkTextIter         *end)
1286 {
1287 	GspellRegionIterReal *real;
1288 	GspellRegionPrivate *priv;
1289 	Subregion *sr;
1290 
1291 	g_return_val_if_fail (iter != NULL, FALSE);
1292 
1293 	real = (GspellRegionIterReal *)iter;
1294 	g_return_val_if_fail (check_iterator (real), FALSE);
1295 
1296 	if (real->subregions == NULL)
1297 	{
1298 		return FALSE;
1299 	}
1300 
1301 	priv = _gspell_region_get_instance_private (real->region);
1302 
1303 	if (priv->buffer == NULL)
1304 	{
1305 		return FALSE;
1306 	}
1307 
1308 	sr = real->subregions->data;
1309 	g_return_val_if_fail (sr != NULL, FALSE);
1310 
1311 	if (start != NULL)
1312 	{
1313 		gtk_text_buffer_get_iter_at_mark (priv->buffer, start, sr->start);
1314 	}
1315 
1316 	if (end != NULL)
1317 	{
1318 		gtk_text_buffer_get_iter_at_mark (priv->buffer, end, sr->end);
1319 	}
1320 
1321 	return TRUE;
1322 }
1323 
1324 /*
1325  * _gspell_region_to_string:
1326  * @region: a #GspellRegion.
1327  *
1328  * Gets a string represention of @region, for debugging purposes.
1329  *
1330  * The returned string contains the character offsets of the subregions. It
1331  * doesn't include a newline character at the end of the string.
1332  *
1333  * Returns: (transfer full) (nullable): a string represention of @region. Free
1334  *   with g_free() when no longer needed.
1335  * Since: 3.22
1336  */
1337 gchar *
_gspell_region_to_string(GspellRegion * region)1338 _gspell_region_to_string (GspellRegion *region)
1339 {
1340 	GspellRegionPrivate *priv;
1341 	GString *string;
1342 	GList *l;
1343 
1344 	g_return_val_if_fail (GSPELL_IS_REGION (region), NULL);
1345 
1346 	priv = _gspell_region_get_instance_private (region);
1347 
1348 	if (priv->buffer == NULL)
1349 	{
1350 		return NULL;
1351 	}
1352 
1353 	string = g_string_new ("Subregions:");
1354 
1355 	for (l = priv->subregions; l != NULL; l = l->next)
1356 	{
1357 		Subregion *sr = l->data;
1358 		GtkTextIter start;
1359 		GtkTextIter end;
1360 
1361 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &start, sr->start);
1362 		gtk_text_buffer_get_iter_at_mark (priv->buffer, &end, sr->end);
1363 
1364 		g_string_append_printf (string,
1365 					" %d-%d",
1366 					gtk_text_iter_get_offset (&start),
1367 					gtk_text_iter_get_offset (&end));
1368 	}
1369 
1370 	return g_string_free (string, FALSE);
1371 }
1372