1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* GTK - The GIMP Toolkit
3  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
4  * Copyright (C) 2019 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library 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 GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 
22 #include "gtktextbtree.h"
23 #include "gtktextbufferprivate.h"
24 #include "gtktextiterprivate.h"
25 #include "gtktextlinedisplaycacheprivate.h"
26 #include "gtkprivate.h"
27 
28 #define DEFAULT_MRU_SIZE         250
29 #define BLOW_CACHE_TIMEOUT_SEC   20
30 #define DEBUG_LINE_DISPLAY_CACHE 0
31 
32 struct _GtkTextLineDisplayCache
33 {
34   GSequence   *sorted_by_line;
35   GHashTable  *line_to_display;
36   GtkTextLine *cursor_line;
37   GQueue       mru;
38   GSource     *evict_source;
39   guint        mru_size;
40 
41 #if DEBUG_LINE_DISPLAY_CACHE
42   guint       log_source;
43   int         hits;
44   int         misses;
45   int         inval;
46   int         inval_cursors;
47   int         inval_by_line;
48   int         inval_by_range;
49   int         inval_by_y_range;
50 #endif
51 };
52 
53 #if DEBUG_LINE_DISPLAY_CACHE
54 # define STAT_ADD(val,n) ((val) += n)
55 # define STAT_INC(val)   STAT_ADD(val,1)
56 static gboolean
dump_stats(gpointer data)57 dump_stats (gpointer data)
58 {
59   GtkTextLineDisplayCache *cache = data;
60   g_printerr ("%p: size=%u hits=%d misses=%d inval_total=%d "
61               "inval_cursors=%d inval_by_line=%d "
62               "inval_by_range=%d inval_by_y_range=%d\n",
63               cache, g_hash_table_size (cache->line_to_display),
64               cache->hits, cache->misses,
65               cache->inval, cache->inval_cursors,
66               cache->inval_by_line, cache->inval_by_range,
67               cache->inval_by_y_range);
68   return G_SOURCE_CONTINUE;
69 }
70 #else
71 # define STAT_ADD(val,n)
72 # define STAT_INC(val)
73 #endif
74 
75 GtkTextLineDisplayCache *
gtk_text_line_display_cache_new(void)76 gtk_text_line_display_cache_new (void)
77 {
78   GtkTextLineDisplayCache *ret;
79 
80   ret = g_slice_new0 (GtkTextLineDisplayCache);
81   ret->sorted_by_line = g_sequence_new ((GDestroyNotify)gtk_text_line_display_unref);
82   ret->line_to_display = g_hash_table_new (NULL, NULL);
83   ret->mru_size = DEFAULT_MRU_SIZE;
84 
85 #if DEBUG_LINE_DISPLAY_CACHE
86   ret->log_source = g_timeout_add_seconds (1, dump_stats, ret);
87 #endif
88 
89   return g_steal_pointer (&ret);
90 }
91 
92 void
gtk_text_line_display_cache_free(GtkTextLineDisplayCache * cache)93 gtk_text_line_display_cache_free (GtkTextLineDisplayCache *cache)
94 {
95 #if DEBUG_LINE_DISPLAY_CACHE
96   g_clear_handle_id (&cache->log_source, g_source_remove);
97 #endif
98 
99   gtk_text_line_display_cache_invalidate (cache);
100 
101   g_clear_pointer (&cache->evict_source, g_source_destroy);
102   g_clear_pointer (&cache->sorted_by_line, g_sequence_free);
103   g_clear_pointer (&cache->line_to_display, g_hash_table_unref);
104   g_slice_free (GtkTextLineDisplayCache, cache);
105 }
106 
107 static gboolean
gtk_text_line_display_cache_blow_cb(gpointer data)108 gtk_text_line_display_cache_blow_cb (gpointer data)
109 {
110   GtkTextLineDisplayCache *cache = data;
111 
112   g_assert (cache != NULL);
113 
114 #if DEBUG_LINE_DISPLAY_CACHE
115   g_printerr ("Evicting GtkTextLineDisplayCache\n");
116 #endif
117 
118   cache->evict_source = NULL;
119 
120   gtk_text_line_display_cache_invalidate (cache);
121 
122   return G_SOURCE_REMOVE;
123 }
124 
125 void
gtk_text_line_display_cache_delay_eviction(GtkTextLineDisplayCache * cache)126 gtk_text_line_display_cache_delay_eviction (GtkTextLineDisplayCache *cache)
127 {
128   g_assert (cache != NULL);
129 
130   if (cache->evict_source != NULL)
131     {
132       gint64 deadline;
133 
134       deadline = g_get_monotonic_time () + (BLOW_CACHE_TIMEOUT_SEC * G_USEC_PER_SEC);
135       g_source_set_ready_time (cache->evict_source, deadline);
136     }
137   else
138     {
139       guint tag;
140 
141       tag = g_timeout_add_seconds (BLOW_CACHE_TIMEOUT_SEC,
142                                    gtk_text_line_display_cache_blow_cb,
143                                    cache);
144       cache->evict_source = g_main_context_find_source_by_id (NULL, tag);
145       g_source_set_static_name (cache->evict_source, "[gtk+] gtk_text_line_display_cache_blow_cb");
146     }
147 }
148 
149 #if DEBUG_LINE_DISPLAY_CACHE
150 static void
check_disposition(GtkTextLineDisplayCache * cache,GtkTextLayout * layout)151 check_disposition (GtkTextLineDisplayCache *cache,
152                    GtkTextLayout           *layout)
153 {
154   GSequenceIter *iter;
155   int last = G_MAXUINT;
156 
157   g_assert (cache != NULL);
158   g_assert (cache->sorted_by_line != NULL);
159   g_assert (cache->line_to_display != NULL);
160 
161   for (iter = g_sequence_get_begin_iter (cache->sorted_by_line);
162        !g_sequence_iter_is_end (iter);
163        iter = g_sequence_iter_next (iter))
164     {
165       GtkTextLineDisplay *display = g_sequence_get (iter);
166       GtkTextIter text_iter;
167       guint line;
168 
169       gtk_text_layout_get_iter_at_line (layout, &text_iter, display->line, 0);
170       line = gtk_text_iter_get_line (&text_iter);
171 
172       g_assert_cmpint (line, >, last);
173 
174       last = line;
175     }
176 }
177 #endif
178 
179 static void
gtk_text_line_display_cache_take_display(GtkTextLineDisplayCache * cache,GtkTextLineDisplay * display,GtkTextLayout * layout)180 gtk_text_line_display_cache_take_display (GtkTextLineDisplayCache *cache,
181                                           GtkTextLineDisplay      *display,
182                                           GtkTextLayout           *layout)
183 {
184   g_assert (cache != NULL);
185   g_assert (display != NULL);
186   g_assert (display->line != NULL);
187   g_assert (display->cache_iter == NULL);
188   g_assert (display->mru_link.data == display);
189   g_assert (display->mru_link.prev == NULL);
190   g_assert (display->mru_link.next == NULL);
191   g_assert (g_hash_table_lookup (cache->line_to_display, display->line) == NULL);
192 
193 #if DEBUG_LINE_DISPLAY_CACHE
194   check_disposition (cache, layout);
195 #endif
196 
197   display->cache_iter =
198     g_sequence_insert_sorted (cache->sorted_by_line,
199                               display,
200                               (GCompareDataFunc) gtk_text_line_display_compare,
201                               layout);
202   g_hash_table_insert (cache->line_to_display, display->line, display);
203   g_queue_push_head_link (&cache->mru, &display->mru_link);
204 
205   /* Cull the cache if we're at capacity */
206   while (cache->mru.length > cache->mru_size)
207     {
208       display = g_queue_peek_tail (&cache->mru);
209 
210       gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
211     }
212 }
213 
214 /*
215  * gtk_text_line_display_cache_invalidate_display:
216  * @cache: a GtkTextLineDisplayCache
217  * @display: a GtkTextLineDisplay
218  * @cursors_only: if only the cursor positions should be invalidated
219  *
220  * If @cursors_only is TRUE, then only the cursors are invalidated. Otherwise,
221  * @display is removed from the cache.
222  *
223  * Use this function when you already have access to a display as it reduces
224  * some overhead.
225  */
226 void
gtk_text_line_display_cache_invalidate_display(GtkTextLineDisplayCache * cache,GtkTextLineDisplay * display,gboolean cursors_only)227 gtk_text_line_display_cache_invalidate_display (GtkTextLineDisplayCache *cache,
228                                                 GtkTextLineDisplay      *display,
229                                                 gboolean                 cursors_only)
230 {
231   g_assert (cache != NULL);
232   g_assert (display != NULL);
233   g_assert (display->line != NULL);
234 
235   if (cursors_only)
236     {
237       g_clear_pointer (&display->cursors, g_array_unref);
238       g_clear_pointer (&display->node, gsk_render_node_unref);
239       display->cursors_invalid = TRUE;
240       display->has_block_cursor = FALSE;
241     }
242   else
243     {
244       GSequenceIter *iter = g_steal_pointer (&display->cache_iter);
245 
246       if (cache->cursor_line == display->line)
247         cache->cursor_line = NULL;
248 
249       g_hash_table_remove (cache->line_to_display, display->line);
250       g_queue_unlink (&cache->mru, &display->mru_link);
251 
252       if (iter != NULL)
253         g_sequence_remove (iter);
254     }
255 
256   STAT_INC (cache->inval);
257 }
258 
259 /*
260  * gtk_text_line_display_cache_get:
261  * @cache: a `GtkTextLineDisplayCache`
262  * @layout: a `GtkTextLayout`
263  * @line: a `GtkTextLine`
264  * @size_only: if only line sizing is needed
265  *
266  * Gets a GtkTextLineDisplay for @line.
267  *
268  * If no cached display exists, a new display will be created.
269  *
270  * It's possible that calling this function will cause some existing
271  * cached displays to be released and destroyed.
272  *
273  * Returns: (transfer full) (not nullable): a `GtkTextLineDisplay`
274  */
275 GtkTextLineDisplay *
gtk_text_line_display_cache_get(GtkTextLineDisplayCache * cache,GtkTextLayout * layout,GtkTextLine * line,gboolean size_only)276 gtk_text_line_display_cache_get (GtkTextLineDisplayCache *cache,
277                                  GtkTextLayout           *layout,
278                                  GtkTextLine             *line,
279                                  gboolean                 size_only)
280 {
281   GtkTextLineDisplay *display;
282 
283   g_assert (cache != NULL);
284   g_assert (layout != NULL);
285   g_assert (line != NULL);
286 
287   display = g_hash_table_lookup (cache->line_to_display, line);
288 
289   if (display != NULL)
290     {
291       if (size_only || !display->size_only)
292         {
293           STAT_INC (cache->hits);
294 
295           if (!size_only && display->line == cache->cursor_line)
296             gtk_text_layout_update_display_cursors (layout, display->line, display);
297 
298           if (!size_only && display->has_children)
299             gtk_text_layout_update_children (layout, display);
300 
301           /* Move to front of MRU */
302           g_queue_unlink (&cache->mru, &display->mru_link);
303           g_queue_push_head_link (&cache->mru, &display->mru_link);
304 
305           return gtk_text_line_display_ref (display);
306         }
307 
308       /* We need an updated display that includes more than just
309        * sizing, so we need to drop this entry and force the layout
310        * to create a new one.
311        */
312       gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
313     }
314 
315   STAT_INC (cache->misses);
316 
317   g_assert (!g_hash_table_lookup (cache->line_to_display, line));
318 
319   display = gtk_text_layout_create_display (layout, line, size_only);
320 
321   g_assert (display != NULL);
322   g_assert (display->line == line);
323 
324   if (!size_only)
325     {
326       if (line == cache->cursor_line)
327         gtk_text_layout_update_display_cursors (layout, line, display);
328 
329       if (display->has_children)
330         gtk_text_layout_update_children (layout, display);
331 
332       gtk_text_line_display_cache_take_display (cache,
333                                                 gtk_text_line_display_ref (display),
334                                                 layout);
335     }
336 
337   return g_steal_pointer (&display);
338 }
339 
340 void
gtk_text_line_display_cache_invalidate(GtkTextLineDisplayCache * cache)341 gtk_text_line_display_cache_invalidate (GtkTextLineDisplayCache *cache)
342 {
343   g_assert (cache != NULL);
344   g_assert (cache->sorted_by_line != NULL);
345   g_assert (cache->line_to_display != NULL);
346 
347   STAT_ADD (cache->inval, g_hash_table_size (cache->line_to_display));
348 
349   cache->cursor_line = NULL;
350 
351   while (cache->mru.head != NULL)
352     {
353       GtkTextLineDisplay *display = g_queue_peek_head (&cache->mru);
354 
355       gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
356     }
357 
358   g_assert (g_hash_table_size (cache->line_to_display) == 0);
359   g_assert (g_sequence_get_length (cache->sorted_by_line) == 0);
360   g_assert (cache->mru.length == 0);
361 }
362 
363 void
gtk_text_line_display_cache_invalidate_cursors(GtkTextLineDisplayCache * cache,GtkTextLine * line)364 gtk_text_line_display_cache_invalidate_cursors (GtkTextLineDisplayCache *cache,
365                                                 GtkTextLine             *line)
366 {
367   GtkTextLineDisplay *display;
368 
369   g_assert (cache != NULL);
370   g_assert (line != NULL);
371 
372   STAT_INC (cache->inval_cursors);
373 
374   display = g_hash_table_lookup (cache->line_to_display, line);
375 
376   if (display != NULL)
377     gtk_text_line_display_cache_invalidate_display (cache, display, TRUE);
378 }
379 
380 /*
381  * gtk_text_line_display_cache_invalidate_line:
382  * @self: a GtkTextLineDisplayCache
383  * @line: a GtkTextLine
384  *
385  * Removes a cached display for @line.
386  *
387  * Compare to gtk_text_line_display_cache_invalidate_cursors() which
388  * only invalidates the cursors for this row.
389  */
390 void
gtk_text_line_display_cache_invalidate_line(GtkTextLineDisplayCache * cache,GtkTextLine * line)391 gtk_text_line_display_cache_invalidate_line (GtkTextLineDisplayCache *cache,
392                                              GtkTextLine             *line)
393 {
394   GtkTextLineDisplay *display;
395 
396   g_assert (cache != NULL);
397   g_assert (line != NULL);
398 
399   display = g_hash_table_lookup (cache->line_to_display, line);
400 
401   if (display != NULL)
402     gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
403 
404   STAT_INC (cache->inval_by_line);
405 }
406 
407 static GSequenceIter *
find_iter_at_text_iter(GtkTextLineDisplayCache * cache,GtkTextLayout * layout,const GtkTextIter * iter)408 find_iter_at_text_iter (GtkTextLineDisplayCache *cache,
409                         GtkTextLayout           *layout,
410                         const GtkTextIter       *iter)
411 {
412   GSequenceIter *left;
413   GSequenceIter *right;
414   GSequenceIter *mid;
415   GSequenceIter *end;
416   GtkTextLine *target;
417   guint target_lineno;
418 
419   g_assert (cache != NULL);
420   g_assert (iter != NULL);
421 
422   if (g_sequence_is_empty (cache->sorted_by_line))
423     return NULL;
424 
425   /* gtk_text_iter_get_line() will have cached value */
426   target_lineno = gtk_text_iter_get_line (iter);
427   target = _gtk_text_iter_get_text_line (iter);
428 
429   /* Get some iters so we can work with pointer compare */
430   end = g_sequence_get_end_iter (cache->sorted_by_line);
431   left = g_sequence_get_begin_iter (cache->sorted_by_line);
432   right = g_sequence_iter_prev (end);
433 
434   /* We already checked for empty above */
435   g_assert (!g_sequence_iter_is_end (left));
436   g_assert (!g_sequence_iter_is_end (right));
437 
438   for (;;)
439     {
440       GtkTextLineDisplay *display;
441       guint lineno;
442 
443       if (left == right)
444         mid = left;
445       else
446         mid = g_sequence_range_get_midpoint (left, right);
447 
448       g_assert (mid != NULL);
449       g_assert (!g_sequence_iter_is_end (mid));
450 
451       if (mid == end)
452         break;
453 
454       display = g_sequence_get (mid);
455 
456       g_assert (display != NULL);
457       g_assert (display->line != NULL);
458       g_assert (display->cache_iter != NULL);
459 
460       if (target == display->line)
461         return mid;
462 
463       if (right == left)
464         break;
465 
466       lineno = _gtk_text_line_get_number (display->line);
467 
468       if (target_lineno < lineno)
469         right = mid;
470       else if (target_lineno > lineno)
471         left = g_sequence_iter_next (mid);
472       else
473         g_assert_not_reached ();
474     }
475 
476   return NULL;
477 }
478 
479 
480 /*
481  * gtk_text_line_display_cache_invalidate_range:
482  * @cache: a GtkTextLineDisplayCache
483  * @begin: the starting text iter
484  * @end: the ending text iter
485  *
486  * Removes all GtkTextLineDisplay that fall between or including
487  * @begin and @end.
488  */
489 void
gtk_text_line_display_cache_invalidate_range(GtkTextLineDisplayCache * cache,GtkTextLayout * layout,const GtkTextIter * begin,const GtkTextIter * end,gboolean cursors_only)490 gtk_text_line_display_cache_invalidate_range (GtkTextLineDisplayCache *cache,
491                                               GtkTextLayout           *layout,
492                                               const GtkTextIter       *begin,
493                                               const GtkTextIter       *end,
494                                               gboolean                 cursors_only)
495 {
496   GSequenceIter *begin_iter;
497   GSequenceIter *end_iter;
498   GSequenceIter *iter;
499 
500   g_assert (cache != NULL);
501   g_assert (layout != NULL);
502   g_assert (begin != NULL);
503   g_assert (end != NULL);
504 
505   STAT_INC (cache->inval_by_range);
506 
507   /* Short-circuit, is_empty() is O(1) */
508   if (g_sequence_is_empty (cache->sorted_by_line))
509     return;
510 
511   /* gtk_text_iter_order() preserving const */
512   if (gtk_text_iter_compare (begin, end) > 0)
513     {
514       const GtkTextIter *tmp = begin;
515       end = begin;
516       begin = tmp;
517     }
518 
519   /* Common case, begin/end on same line. Just try to find the line by
520    * line number and invalidate it alone.
521    */
522   if G_LIKELY (_gtk_text_iter_same_line (begin, end))
523     {
524       begin_iter = find_iter_at_text_iter (cache, layout, begin);
525 
526       if (begin_iter != NULL)
527         {
528           GtkTextLineDisplay *display = g_sequence_get (begin_iter);
529 
530           g_assert (display != NULL);
531           g_assert (display->line != NULL);
532 
533           gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only);
534         }
535 
536       return;
537     }
538 
539   /* Find GSequenceIter containing GtkTextLineDisplay that correspond
540    * to each of the text positions.
541    */
542   begin_iter = find_iter_at_text_iter (cache, layout, begin);
543   end_iter = find_iter_at_text_iter (cache, layout, end);
544 
545   /* Short-circuit if we found nothing */
546   if (begin_iter == NULL && end_iter == NULL)
547     return;
548 
549   /* If nothing matches the end, we need to walk to the end of our
550    * cached displays. We know there is a non-zero number of items
551    * in the sequence at this point, so we can iter_prev() safely.
552    */
553   if (end_iter == NULL)
554     end_iter = g_sequence_iter_prev (g_sequence_get_end_iter (cache->sorted_by_line));
555 
556   /* If nothing matched the begin, we need to walk starting from
557    * the first display we have cached.
558    */
559   if (begin_iter == NULL)
560     begin_iter = g_sequence_get_begin_iter (cache->sorted_by_line);
561 
562   iter = begin_iter;
563 
564   for (;;)
565     {
566       GtkTextLineDisplay *display = g_sequence_get (iter);
567       GSequenceIter *next = g_sequence_iter_next (iter);
568 
569       gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only);
570 
571       if (iter == end_iter)
572         break;
573 
574       iter = next;
575     }
576 }
577 
578 static GSequenceIter *
find_iter_at_at_y(GtkTextLineDisplayCache * cache,GtkTextLayout * layout,int y)579 find_iter_at_at_y (GtkTextLineDisplayCache *cache,
580                    GtkTextLayout           *layout,
581                    int                      y)
582 {
583   GtkTextBTree *btree;
584   GSequenceIter *left;
585   GSequenceIter *right;
586   GSequenceIter *mid;
587   GSequenceIter *end;
588 
589   g_assert (cache != NULL);
590   g_assert (layout != NULL);
591 
592   if (g_sequence_is_empty (cache->sorted_by_line))
593     return NULL;
594 
595   btree = _gtk_text_buffer_get_btree (layout->buffer);
596 
597   /* Get some iters so we can work with pointer compare */
598   end = g_sequence_get_end_iter (cache->sorted_by_line);
599   left = g_sequence_get_begin_iter (cache->sorted_by_line);
600   right = g_sequence_iter_prev (end);
601 
602   /* We already checked for empty above */
603   g_assert (!g_sequence_iter_is_end (left));
604   g_assert (!g_sequence_iter_is_end (right));
605 
606   for (;;)
607     {
608       GtkTextLineDisplay *display;
609       int cache_y;
610       int cache_height;
611 
612       if (left == right)
613         mid = left;
614       else
615         mid = g_sequence_range_get_midpoint (left, right);
616 
617       g_assert (mid != NULL);
618       g_assert (!g_sequence_iter_is_end (mid));
619 
620       if (mid == end)
621         break;
622 
623       display = g_sequence_get (mid);
624 
625       g_assert (display != NULL);
626       g_assert (display->line != NULL);
627 
628       cache_y = _gtk_text_btree_find_line_top (btree, display->line, layout);
629       cache_height = display->height;
630 
631       if (y >= cache_y && y <= (cache_y + cache_height))
632         return mid;
633 
634       if (left == right)
635         break;
636 
637       if (y < cache_y)
638         right = mid;
639       else if (y > (cache_y + cache_height))
640         left = g_sequence_iter_next (mid);
641       else
642         g_assert_not_reached ();
643     }
644 
645   return NULL;
646 }
647 
648 /*
649  * gtk_text_line_display_cache_invalidate_y_range:
650  * @cache: a GtkTextLineDisplayCache
651  * @y: the starting Y position
652  * @old_height: the old height of the range
653  * @new_height: the new height of the range
654  * @cursors_only: if only cursors should be invalidated
655  *
656  * Remove all GtkTextLineDisplay that fall into the range starting
657  * from the Y position to Y+Height.
658  */
659 void
gtk_text_line_display_cache_invalidate_y_range(GtkTextLineDisplayCache * cache,GtkTextLayout * layout,int y,int old_height,int new_height,gboolean cursors_only)660 gtk_text_line_display_cache_invalidate_y_range (GtkTextLineDisplayCache *cache,
661                                                 GtkTextLayout           *layout,
662                                                 int                      y,
663                                                 int                      old_height,
664                                                 int                      new_height,
665                                                 gboolean                 cursors_only)
666 {
667   GSequenceIter *iter;
668   GtkTextBTree *btree;
669 
670   g_assert (cache != NULL);
671   g_assert (layout != NULL);
672 
673   STAT_INC (cache->inval_by_y_range);
674 
675   /* A common pattern is to invalidate the whole buffer using y==0 and
676    * old_height==new_height. So special case that instead of walking through
677    * each display item one at a time.
678    */
679   if (y < 0 || (y == 0 && old_height == new_height))
680     {
681       gtk_text_line_display_cache_invalidate (cache);
682       return;
683     }
684 
685   btree = _gtk_text_buffer_get_btree (layout->buffer);
686   iter = find_iter_at_at_y (cache, layout, y);
687 
688   if (iter == NULL)
689     return;
690 
691   while (!g_sequence_iter_is_end (iter))
692     {
693       GtkTextLineDisplay *display;
694       int cache_y;
695       int cache_height;
696 
697       display = g_sequence_get (iter);
698       iter = g_sequence_iter_next (iter);
699 
700       cache_y = _gtk_text_btree_find_line_top (btree, display->line, layout);
701       cache_height = display->height;
702 
703       if (cache_y + cache_height >= y && cache_y <= y + old_height)
704         {
705           gtk_text_line_display_cache_invalidate_display (cache, display, cursors_only);
706 
707           y += cache_height;
708           old_height -= cache_height;
709 
710           if (old_height > 0)
711             continue;
712         }
713 
714       break;
715     }
716 }
717 
718 void
gtk_text_line_display_cache_set_cursor_line(GtkTextLineDisplayCache * cache,GtkTextLine * cursor_line)719 gtk_text_line_display_cache_set_cursor_line (GtkTextLineDisplayCache *cache,
720                                              GtkTextLine             *cursor_line)
721 {
722   GtkTextLineDisplay *display;
723 
724   g_assert (cache != NULL);
725 
726   if (cursor_line == cache->cursor_line)
727     return;
728 
729   display = g_hash_table_lookup (cache->line_to_display, cache->cursor_line);
730 
731   if (display != NULL)
732     gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
733 
734   cache->cursor_line = cursor_line;
735 
736   display = g_hash_table_lookup (cache->line_to_display, cache->cursor_line);
737 
738   if (display != NULL)
739     gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
740 }
741 
742 void
gtk_text_line_display_cache_set_mru_size(GtkTextLineDisplayCache * cache,guint mru_size)743 gtk_text_line_display_cache_set_mru_size (GtkTextLineDisplayCache *cache,
744                                           guint                    mru_size)
745 {
746   GtkTextLineDisplay *display;
747 
748   g_assert (cache != NULL);
749 
750   if (mru_size == 0)
751     mru_size = DEFAULT_MRU_SIZE;
752 
753   if (mru_size != cache->mru_size)
754     {
755       cache->mru_size = mru_size;
756 
757       while (cache->mru.length > cache->mru_size)
758         {
759           display = g_queue_peek_tail (&cache->mru);
760 
761           gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
762         }
763     }
764 }
765