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
32struct _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)
56static gboolean
57dump_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
75GtkTextLineDisplayCache *
76gtk_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 (data_destroy: (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
92void
93gtk_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
107static gboolean
108gtk_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
125void
126gtk_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 (source: cache->evict_source, ready_time: deadline);
136 }
137 else
138 {
139 guint tag;
140
141 tag = g_timeout_add_seconds (BLOW_CACHE_TIMEOUT_SEC,
142 function: gtk_text_line_display_cache_blow_cb,
143 data: cache);
144 cache->evict_source = g_main_context_find_source_by_id (NULL, source_id: 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
150static void
151check_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
179static void
180gtk_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 (seq: cache->sorted_by_line,
199 data: display,
200 cmp_func: (GCompareDataFunc) gtk_text_line_display_compare,
201 cmp_data: layout);
202 g_hash_table_insert (hash_table: cache->line_to_display, key: display->line, value: display);
203 g_queue_push_head_link (queue: &cache->mru, link_: &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 (queue: &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 */
226void
227gtk_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 (hash_table: cache->line_to_display, key: display->line);
250 g_queue_unlink (queue: &cache->mru, link_: &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 */
275GtkTextLineDisplay *
276gtk_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 (hash_table: cache->line_to_display, key: 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, line: 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 (queue: &cache->mru, link_: &display->mru_link);
303 g_queue_push_head_link (queue: &cache->mru, link_: &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 display: gtk_text_line_display_ref (display),
334 layout);
335 }
336
337 return g_steal_pointer (&display);
338}
339
340void
341gtk_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 (queue: &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
363void
364gtk_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 (hash_table: cache->line_to_display, key: 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 */
390void
391gtk_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 (hash_table: cache->line_to_display, key: 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
407static GSequenceIter *
408find_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 (seq: 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 (seq: cache->sorted_by_line);
431 left = g_sequence_get_begin_iter (seq: cache->sorted_by_line);
432 right = g_sequence_iter_prev (iter: 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 (begin: left, end: 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 (iter: 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 (line: display->line);
467
468 if (target_lineno < lineno)
469 right = mid;
470 else if (target_lineno > lineno)
471 left = g_sequence_iter_next (iter: 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 */
489void
490gtk_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 (seq: cache->sorted_by_line))
509 return;
510
511 /* gtk_text_iter_order() preserving const */
512 if (gtk_text_iter_compare (lhs: begin, rhs: 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, iter: begin);
525
526 if (begin_iter != NULL)
527 {
528 GtkTextLineDisplay *display = g_sequence_get (iter: 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, iter: begin);
543 end_iter = find_iter_at_text_iter (cache, layout, iter: 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 (iter: g_sequence_get_end_iter (seq: 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 (seq: 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
578static GSequenceIter *
579find_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 (seq: cache->sorted_by_line))
593 return NULL;
594
595 btree = _gtk_text_buffer_get_btree (buffer: layout->buffer);
596
597 /* Get some iters so we can work with pointer compare */
598 end = g_sequence_get_end_iter (seq: cache->sorted_by_line);
599 left = g_sequence_get_begin_iter (seq: cache->sorted_by_line);
600 right = g_sequence_iter_prev (iter: 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 (begin: left, end: 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 (iter: mid);
624
625 g_assert (display != NULL);
626 g_assert (display->line != NULL);
627
628 cache_y = _gtk_text_btree_find_line_top (tree: btree, line: display->line, view_id: 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 (iter: 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 */
659void
660gtk_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 (buffer: 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 (tree: btree, line: display->line, view_id: 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
718void
719gtk_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 (hash_table: cache->line_to_display, key: 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 (hash_table: cache->line_to_display, key: cache->cursor_line);
737
738 if (display != NULL)
739 gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
740}
741
742void
743gtk_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 (queue: &cache->mru);
760
761 gtk_text_line_display_cache_invalidate_display (cache, display, FALSE);
762 }
763 }
764}
765

source code of gtk/gtk/gtktextlinedisplaycache.c