1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * plumatextregion.h - GtkTextMark based region utility functions
4 *
5 * This file is part of the GtkSourceView widget
6 *
7 * Copyright (C) 2002 Gustavo Giráldez <gustavo.giraldez@gmx.net>
8 * Copyright (C) 2012-2021 MATE Developers
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program 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
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <glib.h>
31
32 #include "plumatextregion.h"
33
34
35 #undef ENABLE_DEBUG
36 /*
37 #define ENABLE_DEBUG
38 */
39
40 #ifdef ENABLE_DEBUG
41 #define DEBUG(x) (x)
42 #else
43 #define DEBUG(x)
44 #endif
45
46 typedef struct _Subregion {
47 GtkTextMark *start;
48 GtkTextMark *end;
49 } Subregion;
50
51 struct _PlumaTextRegion {
52 GtkTextBuffer *buffer;
53 GList *subregions;
54 guint32 time_stamp;
55 };
56
57 typedef struct _PlumaTextRegionIteratorReal PlumaTextRegionIteratorReal;
58
59 struct _PlumaTextRegionIteratorReal {
60 PlumaTextRegion *region;
61 guint32 region_time_stamp;
62
63 GList *subregions;
64 };
65
66
67 /* ----------------------------------------------------------------------
68 Private interface
69 ---------------------------------------------------------------------- */
70
71 /* Find and return a subregion node which contains the given text
72 iter. If left_side is TRUE, return the subregion which contains
73 the text iter or which is the leftmost; else return the rightmost
74 subregion */
75 static GList *
find_nearest_subregion(PlumaTextRegion * region,const GtkTextIter * iter,GList * begin,gboolean leftmost,gboolean include_edges)76 find_nearest_subregion (PlumaTextRegion *region,
77 const GtkTextIter *iter,
78 GList *begin,
79 gboolean leftmost,
80 gboolean include_edges)
81 {
82 GList *l, *retval;
83
84 g_return_val_if_fail (region != NULL && iter != NULL, NULL);
85
86 if (!begin)
87 begin = region->subregions;
88
89 if (begin)
90 retval = begin->prev;
91 else
92 retval = NULL;
93
94 for (l = begin; l; l = l->next) {
95 GtkTextIter sr_iter;
96 Subregion *sr = l->data;
97 gint cmp;
98
99 if (!leftmost) {
100 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_iter, sr->end);
101 cmp = gtk_text_iter_compare (iter, &sr_iter);
102 if (cmp < 0 || (cmp == 0 && include_edges)) {
103 retval = l;
104 break;
105 }
106
107 } else {
108 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_iter, sr->start);
109 cmp = gtk_text_iter_compare (iter, &sr_iter);
110 if (cmp > 0 || (cmp == 0 && include_edges))
111 retval = l;
112 else
113 break;
114 }
115 }
116 return retval;
117 }
118
119 /* ----------------------------------------------------------------------
120 Public interface
121 ---------------------------------------------------------------------- */
122
123 PlumaTextRegion *
pluma_text_region_new(GtkTextBuffer * buffer)124 pluma_text_region_new (GtkTextBuffer *buffer)
125 {
126 PlumaTextRegion *region;
127
128 g_return_val_if_fail (buffer != NULL, NULL);
129
130 region = g_new (PlumaTextRegion, 1);
131 region->buffer = buffer;
132 region->subregions = NULL;
133 region->time_stamp = 0;
134
135 return region;
136 }
137
138 void
pluma_text_region_destroy(PlumaTextRegion * region,gboolean delete_marks)139 pluma_text_region_destroy (PlumaTextRegion *region, gboolean delete_marks)
140 {
141 g_return_if_fail (region != NULL);
142
143 while (region->subregions) {
144 Subregion *sr = region->subregions->data;
145 if (delete_marks) {
146 gtk_text_buffer_delete_mark (region->buffer, sr->start);
147 gtk_text_buffer_delete_mark (region->buffer, sr->end);
148 }
149 g_free (sr);
150 region->subregions = g_list_delete_link (region->subregions,
151 region->subregions);
152 }
153 region->buffer = NULL;
154 region->time_stamp = 0;
155
156 g_free (region);
157 }
158
159 GtkTextBuffer *
pluma_text_region_get_buffer(PlumaTextRegion * region)160 pluma_text_region_get_buffer (PlumaTextRegion *region)
161 {
162 g_return_val_if_fail (region != NULL, NULL);
163
164 return region->buffer;
165 }
166
167 static void
pluma_text_region_clear_zero_length_subregions(PlumaTextRegion * region)168 pluma_text_region_clear_zero_length_subregions (PlumaTextRegion *region)
169 {
170 GtkTextIter start, end;
171 GList *node;
172
173 g_return_if_fail (region != NULL);
174
175 for (node = region->subregions; node; ) {
176 Subregion *sr = node->data;
177 gtk_text_buffer_get_iter_at_mark (region->buffer, &start, sr->start);
178 gtk_text_buffer_get_iter_at_mark (region->buffer, &end, sr->end);
179 if (gtk_text_iter_equal (&start, &end)) {
180 gtk_text_buffer_delete_mark (region->buffer, sr->start);
181 gtk_text_buffer_delete_mark (region->buffer, sr->end);
182 g_free (sr);
183 if (node == region->subregions)
184 region->subregions = node = g_list_delete_link (node, node);
185 else
186 node = g_list_delete_link (node, node);
187
188 ++region->time_stamp;
189
190 } else {
191 node = node->next;
192 }
193 }
194 }
195
196 void
pluma_text_region_add(PlumaTextRegion * region,const GtkTextIter * _start,const GtkTextIter * _end)197 pluma_text_region_add (PlumaTextRegion *region,
198 const GtkTextIter *_start,
199 const GtkTextIter *_end)
200 {
201 GList *start_node, *end_node;
202 GtkTextIter start, end;
203
204 g_return_if_fail (region != NULL && _start != NULL && _end != NULL);
205
206 start = *_start;
207 end = *_end;
208
209 DEBUG (g_print ("---\n"));
210 DEBUG (pluma_text_region_debug_print (region));
211 DEBUG (g_message ("region_add (%d, %d)",
212 gtk_text_iter_get_offset (&start),
213 gtk_text_iter_get_offset (&end)));
214
215 gtk_text_iter_order (&start, &end);
216
217 /* don't add zero-length regions */
218 if (gtk_text_iter_equal (&start, &end))
219 return;
220
221 /* find bounding subregions */
222 start_node = find_nearest_subregion (region, &start, NULL, FALSE, TRUE);
223 end_node = find_nearest_subregion (region, &end, start_node, TRUE, TRUE);
224
225 if (start_node == NULL || end_node == NULL || end_node == start_node->prev) {
226 /* create the new subregion */
227 Subregion *sr = g_new0 (Subregion, 1);
228 sr->start = gtk_text_buffer_create_mark (region->buffer, NULL, &start, TRUE);
229 sr->end = gtk_text_buffer_create_mark (region->buffer, NULL, &end, FALSE);
230
231 if (start_node == NULL) {
232 /* append the new region */
233 region->subregions = g_list_append (region->subregions, sr);
234
235 } else if (end_node == NULL) {
236 /* prepend the new region */
237 region->subregions = g_list_prepend (region->subregions, sr);
238
239 } else {
240 /* we are in the middle of two subregions */
241 region->subregions = g_list_insert_before (region->subregions,
242 start_node, sr);
243 }
244 }
245 else {
246 GtkTextIter iter;
247 Subregion *sr = start_node->data;
248 if (start_node != end_node) {
249 /* we need to merge some subregions */
250 GList *l = start_node->next;
251 Subregion *q;
252
253 gtk_text_buffer_delete_mark (region->buffer, sr->end);
254 while (l != end_node) {
255 q = l->data;
256 gtk_text_buffer_delete_mark (region->buffer, q->start);
257 gtk_text_buffer_delete_mark (region->buffer, q->end);
258 g_free (q);
259 l = g_list_delete_link (l, l);
260 }
261 q = l->data;
262 gtk_text_buffer_delete_mark (region->buffer, q->start);
263 sr->end = q->end;
264 g_free (q);
265 l = g_list_delete_link (l, l);
266 }
267 /* now move marks if that action expands the region */
268 gtk_text_buffer_get_iter_at_mark (region->buffer, &iter, sr->start);
269 if (gtk_text_iter_compare (&iter, &start) > 0)
270 gtk_text_buffer_move_mark (region->buffer, sr->start, &start);
271 gtk_text_buffer_get_iter_at_mark (region->buffer, &iter, sr->end);
272 if (gtk_text_iter_compare (&iter, &end) < 0)
273 gtk_text_buffer_move_mark (region->buffer, sr->end, &end);
274 }
275
276 ++region->time_stamp;
277
278 DEBUG (pluma_text_region_debug_print (region));
279 }
280
281 void
pluma_text_region_subtract(PlumaTextRegion * region,const GtkTextIter * _start,const GtkTextIter * _end)282 pluma_text_region_subtract (PlumaTextRegion *region,
283 const GtkTextIter *_start,
284 const GtkTextIter *_end)
285 {
286 GList *start_node, *end_node, *node;
287 GtkTextIter sr_start_iter, sr_end_iter;
288 gboolean done;
289 gboolean start_is_outside, end_is_outside;
290 Subregion *sr;
291 GtkTextIter start, end;
292
293 g_return_if_fail (region != NULL && _start != NULL && _end != NULL);
294
295 start = *_start;
296 end = *_end;
297
298 DEBUG (g_print ("---\n"));
299 DEBUG (pluma_text_region_debug_print (region));
300 DEBUG (g_message ("region_substract (%d, %d)",
301 gtk_text_iter_get_offset (&start),
302 gtk_text_iter_get_offset (&end)));
303
304 gtk_text_iter_order (&start, &end);
305
306 /* find bounding subregions */
307 start_node = find_nearest_subregion (region, &start, NULL, FALSE, FALSE);
308 end_node = find_nearest_subregion (region, &end, start_node, TRUE, FALSE);
309
310 /* easy case first */
311 if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
312 return;
313
314 /* deal with the start point */
315 start_is_outside = end_is_outside = FALSE;
316
317 sr = start_node->data;
318 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
319 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
320
321 if (gtk_text_iter_in_range (&start, &sr_start_iter, &sr_end_iter) &&
322 !gtk_text_iter_equal (&start, &sr_start_iter)) {
323 /* the starting point is inside the first subregion */
324 if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter) &&
325 !gtk_text_iter_equal (&end, &sr_end_iter)) {
326 /* the ending point is also inside the first
327 subregion: we need to split */
328 Subregion *new_sr = g_new0 (Subregion, 1);
329 new_sr->end = sr->end;
330 new_sr->start = gtk_text_buffer_create_mark (region->buffer,
331 NULL, &end, TRUE);
332 start_node = g_list_insert_before (start_node, start_node->next, new_sr);
333
334 sr->end = gtk_text_buffer_create_mark (region->buffer,
335 NULL, &start, FALSE);
336
337 /* no further processing needed */
338 DEBUG (g_message ("subregion splitted"));
339
340 return;
341 } else {
342 /* the ending point is outside, so just move
343 the end of the subregion to the starting point */
344 gtk_text_buffer_move_mark (region->buffer, sr->end, &start);
345 }
346 } else {
347 /* the starting point is outside (and so to the left)
348 of the first subregion */
349 DEBUG (g_message ("start is outside"));
350
351 start_is_outside = TRUE;
352 }
353
354 /* deal with the end point */
355 if (start_node != end_node) {
356 sr = end_node->data;
357 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
358 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
359 }
360
361 if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter) &&
362 !gtk_text_iter_equal (&end, &sr_end_iter)) {
363 /* ending point is inside, move the start mark */
364 gtk_text_buffer_move_mark (region->buffer, sr->start, &end);
365 } else {
366 end_is_outside = TRUE;
367 DEBUG (g_message ("end is outside"));
368
369 }
370
371 /* finally remove any intermediate subregions */
372 done = FALSE;
373 node = start_node;
374
375 while (!done) {
376 if (node == end_node)
377 /* we are done, exit in the next iteration */
378 done = TRUE;
379
380 if ((node == start_node && !start_is_outside) ||
381 (node == end_node && !end_is_outside)) {
382 /* skip starting or ending node */
383 node = node->next;
384 } else {
385 GList *l = node->next;
386 sr = node->data;
387 gtk_text_buffer_delete_mark (region->buffer, sr->start);
388 gtk_text_buffer_delete_mark (region->buffer, sr->end);
389 g_free (sr);
390 region->subregions = g_list_delete_link (region->subregions,
391 node);
392 node = l;
393 }
394 }
395
396 ++region->time_stamp;
397
398 DEBUG (pluma_text_region_debug_print (region));
399
400 /* now get rid of empty subregions */
401 pluma_text_region_clear_zero_length_subregions (region);
402
403 DEBUG (pluma_text_region_debug_print (region));
404 }
405
406 gint
pluma_text_region_subregions(PlumaTextRegion * region)407 pluma_text_region_subregions (PlumaTextRegion *region)
408 {
409 g_return_val_if_fail (region != NULL, 0);
410
411 return g_list_length (region->subregions);
412 }
413
414 gboolean
pluma_text_region_nth_subregion(PlumaTextRegion * region,guint subregion,GtkTextIter * start,GtkTextIter * end)415 pluma_text_region_nth_subregion (PlumaTextRegion *region,
416 guint subregion,
417 GtkTextIter *start,
418 GtkTextIter *end)
419 {
420 Subregion *sr;
421
422 g_return_val_if_fail (region != NULL, FALSE);
423
424 sr = g_list_nth_data (region->subregions, subregion);
425 if (sr == NULL)
426 return FALSE;
427
428 if (start)
429 gtk_text_buffer_get_iter_at_mark (region->buffer, start, sr->start);
430 if (end)
431 gtk_text_buffer_get_iter_at_mark (region->buffer, end, sr->end);
432
433 return TRUE;
434 }
435
436 PlumaTextRegion *
pluma_text_region_intersect(PlumaTextRegion * region,const GtkTextIter * _start,const GtkTextIter * _end)437 pluma_text_region_intersect (PlumaTextRegion *region,
438 const GtkTextIter *_start,
439 const GtkTextIter *_end)
440 {
441 GList *start_node, *end_node, *node;
442 GtkTextIter sr_start_iter, sr_end_iter;
443 Subregion *sr, *new_sr;
444 gboolean done;
445 PlumaTextRegion *new_region;
446 GtkTextIter start, end;
447
448 g_return_val_if_fail (region != NULL && _start != NULL && _end != NULL, NULL);
449
450 start = *_start;
451 end = *_end;
452
453 gtk_text_iter_order (&start, &end);
454
455 /* find bounding subregions */
456 start_node = find_nearest_subregion (region, &start, NULL, FALSE, FALSE);
457 end_node = find_nearest_subregion (region, &end, start_node, TRUE, FALSE);
458
459 /* easy case first */
460 if (start_node == NULL || end_node == NULL || end_node == start_node->prev)
461 return NULL;
462
463 new_region = pluma_text_region_new (region->buffer);
464 done = FALSE;
465
466 sr = start_node->data;
467 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
468 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
469
470 /* starting node */
471 if (gtk_text_iter_in_range (&start, &sr_start_iter, &sr_end_iter)) {
472 new_sr = g_new0 (Subregion, 1);
473 new_region->subregions = g_list_prepend (new_region->subregions, new_sr);
474
475 new_sr->start = gtk_text_buffer_create_mark (new_region->buffer, NULL,
476 &start, TRUE);
477 if (start_node == end_node) {
478 /* things will finish shortly */
479 done = TRUE;
480 if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter))
481 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer,
482 NULL, &end, FALSE);
483 else
484 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer,
485 NULL, &sr_end_iter,
486 FALSE);
487 } else {
488 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
489 &sr_end_iter, FALSE);
490 }
491 node = start_node->next;
492 } else {
493 /* start should be the same as the subregion, so copy it in the loop */
494 node = start_node;
495 }
496
497 if (!done) {
498 while (node != end_node) {
499 /* copy intermediate subregions verbatim */
500 sr = node->data;
501 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter,
502 sr->start);
503 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
504
505 new_sr = g_new0 (Subregion, 1);
506 new_region->subregions = g_list_prepend (new_region->subregions, new_sr);
507 new_sr->start = gtk_text_buffer_create_mark (new_region->buffer, NULL,
508 &sr_start_iter, TRUE);
509 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
510 &sr_end_iter, FALSE);
511 /* next node */
512 node = node->next;
513 }
514
515 /* ending node */
516 sr = node->data;
517 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_start_iter, sr->start);
518 gtk_text_buffer_get_iter_at_mark (region->buffer, &sr_end_iter, sr->end);
519
520 new_sr = g_new0 (Subregion, 1);
521 new_region->subregions = g_list_prepend (new_region->subregions, new_sr);
522
523 new_sr->start = gtk_text_buffer_create_mark (new_region->buffer, NULL,
524 &sr_start_iter, TRUE);
525
526 if (gtk_text_iter_in_range (&end, &sr_start_iter, &sr_end_iter))
527 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
528 &end, FALSE);
529 else
530 new_sr->end = gtk_text_buffer_create_mark (new_region->buffer, NULL,
531 &sr_end_iter, FALSE);
532 }
533
534 new_region->subregions = g_list_reverse (new_region->subregions);
535 return new_region;
536 }
537
538 static gboolean
check_iterator(PlumaTextRegionIteratorReal * real)539 check_iterator (PlumaTextRegionIteratorReal *real)
540 {
541 if ((real->region == NULL) ||
542 (real->region_time_stamp != real->region->time_stamp))
543 {
544 g_warning("Invalid iterator: either the iterator "
545 "is uninitialized, or the region "
546 "has been modified since the iterator "
547 "was created.");
548
549 return FALSE;
550 }
551
552 return TRUE;
553 }
554
555 void
pluma_text_region_get_iterator(PlumaTextRegion * region,PlumaTextRegionIterator * iter,guint start)556 pluma_text_region_get_iterator (PlumaTextRegion *region,
557 PlumaTextRegionIterator *iter,
558 guint start)
559 {
560 PlumaTextRegionIteratorReal *real;
561
562 g_return_if_fail (region != NULL);
563 g_return_if_fail (iter != NULL);
564
565 real = (PlumaTextRegionIteratorReal *)iter;
566
567 /* region->subregions may be NULL, -> end iter */
568
569 real->region = region;
570 real->subregions = g_list_nth (region->subregions, start);
571 real->region_time_stamp = region->time_stamp;
572 }
573
574 gboolean
pluma_text_region_iterator_is_end(PlumaTextRegionIterator * iter)575 pluma_text_region_iterator_is_end (PlumaTextRegionIterator *iter)
576 {
577 PlumaTextRegionIteratorReal *real;
578
579 g_return_val_if_fail (iter != NULL, FALSE);
580
581 real = (PlumaTextRegionIteratorReal *)iter;
582 g_return_val_if_fail (check_iterator (real), FALSE);
583
584 return (real->subregions == NULL);
585 }
586
587 gboolean
pluma_text_region_iterator_next(PlumaTextRegionIterator * iter)588 pluma_text_region_iterator_next (PlumaTextRegionIterator *iter)
589 {
590 PlumaTextRegionIteratorReal *real;
591
592 g_return_val_if_fail (iter != NULL, FALSE);
593
594 real = (PlumaTextRegionIteratorReal *)iter;
595 g_return_val_if_fail (check_iterator (real), FALSE);
596
597 if (real->subregions != NULL) {
598 real->subregions = g_list_next (real->subregions);
599 return TRUE;
600 }
601 else
602 return FALSE;
603 }
604
605 void
pluma_text_region_iterator_get_subregion(PlumaTextRegionIterator * iter,GtkTextIter * start,GtkTextIter * end)606 pluma_text_region_iterator_get_subregion (PlumaTextRegionIterator *iter,
607 GtkTextIter *start,
608 GtkTextIter *end)
609 {
610 PlumaTextRegionIteratorReal *real;
611 Subregion *sr;
612
613 g_return_if_fail (iter != NULL);
614
615 real = (PlumaTextRegionIteratorReal *)iter;
616 g_return_if_fail (check_iterator (real));
617 g_return_if_fail (real->subregions != NULL);
618
619 sr = (Subregion*)real->subregions->data;
620 g_return_if_fail (sr != NULL);
621
622 if (start)
623 gtk_text_buffer_get_iter_at_mark (real->region->buffer, start, sr->start);
624 if (end)
625 gtk_text_buffer_get_iter_at_mark (real->region->buffer, end, sr->end);
626 }
627
628 void
pluma_text_region_debug_print(PlumaTextRegion * region)629 pluma_text_region_debug_print (PlumaTextRegion *region)
630 {
631 GList *l;
632
633 g_return_if_fail (region != NULL);
634
635 g_print ("Subregions: ");
636 l = region->subregions;
637 while (l) {
638 Subregion *sr = l->data;
639 GtkTextIter iter1, iter2;
640 gtk_text_buffer_get_iter_at_mark (region->buffer, &iter1, sr->start);
641 gtk_text_buffer_get_iter_at_mark (region->buffer, &iter2, sr->end);
642 g_print ("%d-%d ", gtk_text_iter_get_offset (&iter1),
643 gtk_text_iter_get_offset (&iter2));
644 l = l->next;
645 }
646 g_print ("\n");
647 }
648