1 /* ide-highlight-engine.c
2 *
3 * Copyright 2015-2019 Christian Hergert <christian@hergert.me>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #define G_LOG_DOMAIN "ide-highlight-engine"
22
23 #include "config.h"
24
25 #include <dazzle.h>
26 #include <glib/gi18n.h>
27 #include <gtksourceview/gtksource.h>
28 #include <libide-plugins.h>
29 #include <string.h>
30
31 #include "ide-buffer.h"
32 #include "ide-buffer-private.h"
33 #include "ide-highlight-engine.h"
34 #include "ide-highlight-index.h"
35 #include "ide-highlighter.h"
36
37 #define HIGHLIGHT_QUANTA_USEC 5000
38 #define PRIVATE_TAG_PREFIX "gb-private-tag"
39
40 struct _IdeHighlightEngine
41 {
42 IdeObject parent_instance;
43
44 GWeakRef buffer_wref;
45
46 DzlSignalGroup *signal_group;
47 IdeHighlighter *highlighter;
48 GSettings *settings;
49
50 IdeExtensionAdapter *extension;
51
52 GtkTextMark *invalid_begin;
53 GtkTextMark *invalid_end;
54
55 GSList *private_tags;
56 GSList *public_tags;
57
58 gint64 quanta_expiration;
59
60 guint work_timeout;
61
62 guint enabled : 1;
63 };
64
65 G_DEFINE_FINAL_TYPE (IdeHighlightEngine, ide_highlight_engine, IDE_TYPE_OBJECT)
66
67 enum {
68 PROP_0,
69 PROP_BUFFER,
70 PROP_HIGHLIGHTER,
71 LAST_PROP
72 };
73
74 static GParamSpec *properties [LAST_PROP];
75
76 static gboolean
get_invalidation_area(GtkTextIter * begin,GtkTextIter * end)77 get_invalidation_area (GtkTextIter *begin,
78 GtkTextIter *end)
79 {
80 GtkTextIter begin_tmp;
81 GtkTextIter end_tmp;
82
83 g_assert (begin != NULL);
84 g_assert (end != NULL);
85
86 /*
87 * Move to the beginning of line.We dont use gtk_text_iter_backward_line
88 * because if begin is at the beginning of the line we dont want to
89 * move to the previous line
90 */
91 gtk_text_iter_set_line_offset (begin, 0);
92
93 /* Move to the beginning of the next line. */
94 gtk_text_iter_forward_line (end);
95
96 /* Save the original locations. We will need them down the line. */
97 begin_tmp = *begin;
98 end_tmp = *end;
99
100 /*
101 * Fordward begin iter character by character until:
102 * - We reach a non space character
103 * - We reach end iter
104 */
105 while (g_unichar_isspace (gtk_text_iter_get_char (begin)) &&
106 gtk_text_iter_compare (begin, &end_tmp) < 0)
107 gtk_text_iter_forward_char (begin);
108
109
110 /*
111 * If after moving forward the begin iter, we reached the end iter,
112 * there is no need to play with the end iter.
113 */
114 if (gtk_text_iter_compare (begin, end) < 0)
115 {
116 /*
117 * Backward end iter character by character until:
118 * - We reach a non space character
119 * - We reach begin iter
120 */
121 while (g_unichar_isspace (gtk_text_iter_get_char (end)) &&
122 gtk_text_iter_compare (end, &begin_tmp) > 0)
123 gtk_text_iter_backward_char (end);
124
125 /*
126 * If we found the character we are looking for then move one
127 * character forward in order to include it as the last
128 * character of the begin - end range.
129 */
130 if (gtk_text_iter_compare (end, &end_tmp) < 0)
131 gtk_text_iter_forward_char (end);
132 }
133
134 return gtk_text_iter_compare (begin, end) < 0;
135 }
136
137 static void
sync_tag_style(GtkSourceStyleScheme * style_scheme,GtkTextTag * tag)138 sync_tag_style (GtkSourceStyleScheme *style_scheme,
139 GtkTextTag *tag)
140 {
141 g_autofree gchar *foreground = NULL;
142 g_autofree gchar *background = NULL;
143 g_autofree gchar *tag_name = NULL;
144 gchar *style_name = NULL;
145 const gchar *colon;
146 GtkSourceStyle *style;
147 gboolean foreground_set = FALSE;
148 gboolean background_set = FALSE;
149 gboolean bold = FALSE;
150 gboolean bold_set = FALSE;
151 gboolean underline = FALSE;
152 gboolean underline_set = FALSE;
153 gboolean italic = FALSE;
154 gboolean italic_set = FALSE;
155 gsize tag_name_len;
156 gsize prefix_len;
157
158 g_object_set (tag,
159 "foreground-set", FALSE,
160 "background-set", FALSE,
161 "weight-set", FALSE,
162 "underline-set", FALSE,
163 "style-set", FALSE,
164 NULL);
165
166 g_object_get (tag, "name", &tag_name, NULL);
167
168 if (tag_name == NULL || style_scheme == NULL)
169 return;
170
171 prefix_len = strlen (PRIVATE_TAG_PREFIX);
172 tag_name_len = strlen (tag_name);
173 style_name = tag_name;
174
175 /*
176 * Check if this is a private tag.A tag is private if it starts with
177 * PRIVATE_TAG_PREFIX "gb-private-tag".
178 * ex: gb-private-tag:c:boolean
179 * If the tag is private extract the original style name by moving the string
180 * strlen (PRIVATE_TAG_PREFIX) + 1 (the colon) characters.
181 */
182 if (tag_name_len > prefix_len && memcmp (tag_name, PRIVATE_TAG_PREFIX, prefix_len) == 0)
183 style_name = tag_name + prefix_len + 1;
184
185 style = gtk_source_style_scheme_get_style (style_scheme, style_name);
186 if (style == NULL && (colon = strchr (style_name, ':')))
187 {
188 gchar defname[64];
189 g_snprintf (defname, sizeof defname, "def%s", colon);
190 style = gtk_source_style_scheme_get_style (style_scheme, defname);
191 if (style == NULL)
192 return;
193 }
194
195 g_object_get (style,
196 "background", &background,
197 "background-set", &background_set,
198 "foreground", &foreground,
199 "foreground-set", &foreground_set,
200 "bold", &bold,
201 "bold-set", &bold_set,
202 "pango-underline", &underline,
203 "underline-set", &underline_set,
204 "italic", &italic,
205 "italic-set", &italic_set,
206 NULL);
207
208 if (background_set)
209 g_object_set (tag, "background", background, NULL);
210
211 if (foreground_set)
212 g_object_set (tag, "foreground", foreground, NULL);
213
214 if (bold_set && bold)
215 g_object_set (tag, "weight", PANGO_WEIGHT_BOLD, NULL);
216
217 if (italic_set && italic)
218 g_object_set (tag, "style", PANGO_STYLE_ITALIC, NULL);
219
220 if (underline_set && underline)
221 g_object_set (tag, "underline", PANGO_UNDERLINE_SINGLE, NULL);
222 }
223
224 static GtkTextTag *
create_tag_from_style(IdeHighlightEngine * self,const gchar * style_name)225 create_tag_from_style (IdeHighlightEngine *self,
226 const gchar *style_name)
227 {
228 g_autoptr(IdeBuffer) buffer = NULL;
229 GtkSourceStyleScheme *style_scheme;
230 GtkTextTag *tag = NULL;
231
232 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
233 g_assert (style_name != NULL);
234
235 buffer = g_weak_ref_get (&self->buffer_wref);
236
237 if (buffer != NULL)
238 {
239 tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), style_name, NULL);
240 gtk_text_tag_set_priority (tag, 0);
241 style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
242 sync_tag_style (style_scheme, tag);
243 }
244
245 return tag;
246 }
247
248 static GtkTextTag *
get_tag_from_style(IdeHighlightEngine * self,const gchar * style_name,gboolean private_tag)249 get_tag_from_style (IdeHighlightEngine *self,
250 const gchar *style_name,
251 gboolean private_tag)
252 {
253 g_autoptr(IdeBuffer) buffer = NULL;
254 g_autofree gchar *tmp_style_name = NULL;
255 GtkTextTagTable *tag_table;
256 GtkTextTag *tag;
257
258 g_return_val_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self), NULL);
259 g_return_val_if_fail (style_name != NULL, NULL);
260
261 buffer = g_weak_ref_get (&self->buffer_wref);
262 if (buffer == NULL)
263 return NULL;
264
265 /*
266 * If is private tag prepend the PRIVATE_TAG_PREFIX (gb-private-tag)
267 * to the string.This is used because tag name is the key used
268 * for saving tags in GtkTextTagTable and we dont want conflicts between
269 * public and private tags.
270 */
271 if (private_tag)
272 tmp_style_name = g_strdup_printf ("%s:%s", PRIVATE_TAG_PREFIX, style_name);
273 else
274 tmp_style_name = g_strdup (style_name);
275
276 tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer));
277 tag = gtk_text_tag_table_lookup (tag_table, tmp_style_name);
278
279 if (tag == NULL)
280 {
281 tag = create_tag_from_style (self, tmp_style_name);
282 if (private_tag)
283 self->private_tags = g_slist_prepend (self->private_tags, tag);
284 else
285 self->public_tags = g_slist_prepend (self->public_tags, tag);
286 }
287
288 return tag;
289 }
290
291
292 static IdeHighlightResult
ide_highlight_engine_apply_style(const GtkTextIter * begin,const GtkTextIter * end,const gchar * style_name)293 ide_highlight_engine_apply_style (const GtkTextIter *begin,
294 const GtkTextIter *end,
295 const gchar *style_name)
296 {
297 IdeHighlightEngine *self;
298 GtkTextBuffer *buffer;
299 GtkTextTag *tag;
300
301 buffer = gtk_text_iter_get_buffer (begin);
302 self = _ide_buffer_get_highlight_engine (IDE_BUFFER (buffer));
303 tag = get_tag_from_style (self, style_name, TRUE);
304
305 gtk_text_buffer_apply_tag (buffer, tag, begin, end);
306
307 if (g_get_monotonic_time () >= self->quanta_expiration)
308 return IDE_HIGHLIGHT_STOP;
309
310 return IDE_HIGHLIGHT_CONTINUE;
311 }
312
313 static gboolean
ide_highlight_engine_tick(IdeHighlightEngine * self)314 ide_highlight_engine_tick (IdeHighlightEngine *self)
315 {
316 g_autoptr(GtkTextBuffer) buffer = NULL;
317 GtkTextIter iter;
318 GtkTextIter invalid_begin;
319 GtkTextIter invalid_end;
320 GSList *tags_iter;
321
322 IDE_PROBE;
323
324 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
325 g_assert (self->highlighter != NULL);
326 g_assert (self->invalid_begin != NULL);
327 g_assert (self->invalid_end != NULL);
328
329 buffer = g_weak_ref_get (&self->buffer_wref);
330 if (buffer == NULL)
331 return G_SOURCE_REMOVE;
332
333 self->quanta_expiration = g_get_monotonic_time () + HIGHLIGHT_QUANTA_USEC;
334
335 gtk_text_buffer_get_iter_at_mark (buffer, &invalid_begin, self->invalid_begin);
336 gtk_text_buffer_get_iter_at_mark (buffer, &invalid_end, self->invalid_end);
337
338 IDE_TRACE_MSG ("Highlight Range [%u:%u,%u:%u] (%s)",
339 gtk_text_iter_get_line (&invalid_begin),
340 gtk_text_iter_get_line_offset (&invalid_begin),
341 gtk_text_iter_get_line (&invalid_end),
342 gtk_text_iter_get_line_offset (&invalid_end),
343 G_OBJECT_TYPE_NAME (self->highlighter));
344
345 if (gtk_text_iter_compare (&invalid_begin, &invalid_end) >= 0)
346 IDE_GOTO (up_to_date);
347
348 /* Clear all our tags */
349 for (tags_iter = self->private_tags; tags_iter; tags_iter = tags_iter->next)
350 gtk_text_buffer_remove_tag (buffer,
351 GTK_TEXT_TAG (tags_iter->data),
352 &invalid_begin,
353 &invalid_end);
354
355 iter = invalid_begin;
356
357 ide_highlighter_update (self->highlighter, ide_highlight_engine_apply_style,
358 &invalid_begin, &invalid_end, &iter);
359
360 if (gtk_text_iter_compare (&iter, &invalid_end) >= 0)
361 IDE_GOTO (up_to_date);
362
363 /* Stop processing until further instruction if no movement was made */
364 if (gtk_text_iter_equal (&iter, &invalid_begin))
365 return G_SOURCE_REMOVE;
366
367 gtk_text_buffer_move_mark (buffer, self->invalid_begin, &iter);
368
369 return G_SOURCE_CONTINUE;
370
371 up_to_date:
372 gtk_text_buffer_get_start_iter (buffer, &iter);
373 gtk_text_buffer_move_mark (buffer, self->invalid_begin, &iter);
374 gtk_text_buffer_move_mark (buffer, self->invalid_end, &iter);
375
376 return G_SOURCE_REMOVE;
377 }
378
379 static gboolean
ide_highlight_engine_work_timeout_handler(gpointer data)380 ide_highlight_engine_work_timeout_handler (gpointer data)
381 {
382 IdeHighlightEngine *self = data;
383
384 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
385
386 if (self->enabled)
387 {
388 if (ide_highlight_engine_tick (self))
389 return G_SOURCE_CONTINUE;
390 }
391
392 self->work_timeout = 0;
393
394 return G_SOURCE_REMOVE;
395 }
396
397 static void
ide_highlight_engine_queue_work(IdeHighlightEngine * self)398 ide_highlight_engine_queue_work (IdeHighlightEngine *self)
399 {
400 g_autoptr(GtkTextBuffer) buffer = NULL;
401
402 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
403
404 buffer = g_weak_ref_get (&self->buffer_wref);
405 if (self->highlighter == NULL || buffer == NULL || self->work_timeout != 0)
406 return;
407
408 /*
409 * NOTE: It would be really nice if we could use the GdkFrameClock here to
410 * drive the next update instead of a timeout. It's possible that our
411 * callback could get scheduled right before the frame processing would
412 * begin. However, since that gets driven by something like a Wayland
413 * callback, it won't yet be scheduled. So instead our function gets
414 * called and we potentially cause a frame to drop.
415 */
416
417 self->work_timeout = gdk_threads_add_idle_full (G_PRIORITY_LOW + 1,
418 ide_highlight_engine_work_timeout_handler,
419 self,
420 NULL);
421 }
422
423 /**
424 * ide_highlight_engine_advance:
425 * @self: a #IdeHighlightEngine
426 *
427 * This function is useful for #IdeHighlighter implementations that need to
428 * asynchronously do work to process the highlighting.
429 *
430 * If they return from their update function without advancing, nothing will
431 * happen until they call this method to proceed.
432 *
433 * Since: 3.32
434 */
435 void
ide_highlight_engine_advance(IdeHighlightEngine * self)436 ide_highlight_engine_advance (IdeHighlightEngine *self)
437 {
438 g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
439
440 ide_highlight_engine_queue_work (self);
441 }
442
443 static gboolean
invalidate_and_highlight(IdeHighlightEngine * self,GtkTextIter * begin,GtkTextIter * end)444 invalidate_and_highlight (IdeHighlightEngine *self,
445 GtkTextIter *begin,
446 GtkTextIter *end)
447 {
448 GtkTextBuffer *text_buffer;
449
450 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
451 g_assert (begin != NULL);
452 g_assert (end != NULL);
453
454 if (!self->enabled)
455 return FALSE;
456
457 text_buffer = gtk_text_iter_get_buffer (begin);
458
459 if (get_invalidation_area (begin, end))
460 {
461 GtkTextIter begin_tmp;
462 GtkTextIter end_tmp;
463
464 gtk_text_buffer_get_iter_at_mark (text_buffer, &begin_tmp, self->invalid_begin);
465 gtk_text_buffer_get_iter_at_mark (text_buffer, &end_tmp, self->invalid_end);
466
467 if (gtk_text_iter_equal (&begin_tmp, &end_tmp))
468 {
469 gtk_text_buffer_move_mark (text_buffer, self->invalid_begin, begin);
470 gtk_text_buffer_move_mark (text_buffer, self->invalid_end, end);
471 }
472 else
473 {
474 if (gtk_text_iter_compare (begin, &begin_tmp) < 0)
475 gtk_text_buffer_move_mark (text_buffer, self->invalid_begin, begin);
476 if (gtk_text_iter_compare (end, &end_tmp) > 0)
477 gtk_text_buffer_move_mark (text_buffer, self->invalid_end, end);
478 }
479
480 ide_highlight_engine_queue_work (self);
481
482 return TRUE;
483 }
484
485 return FALSE;
486 }
487
488 static void
ide_highlight_engine_reload(IdeHighlightEngine * self)489 ide_highlight_engine_reload (IdeHighlightEngine *self)
490 {
491 g_autoptr(GtkTextBuffer) buffer = NULL;
492 GtkTextIter begin;
493 GtkTextIter end;
494 GSList *iter;
495
496 IDE_ENTRY;
497
498 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
499
500 dzl_clear_source (&self->work_timeout);
501
502 buffer = g_weak_ref_get (&self->buffer_wref);
503 if (buffer == NULL)
504 IDE_EXIT;
505
506 gtk_text_buffer_get_bounds (buffer, &begin, &end);
507
508 /*
509 * Invalidate the whole buffer.
510 */
511 gtk_text_buffer_move_mark (buffer, self->invalid_begin, &begin);
512 gtk_text_buffer_move_mark (buffer, self->invalid_end, &end);
513
514 /*
515 * Remove our highlight tags from the buffer.
516 */
517 for (iter = self->private_tags; iter; iter = iter->next)
518 gtk_text_buffer_remove_tag (buffer, iter->data, &begin, &end);
519 g_clear_pointer (&self->private_tags, g_slist_free);
520
521 for (iter = self->public_tags; iter; iter = iter->next)
522 gtk_text_buffer_remove_tag (buffer, iter->data, &begin, &end);
523 g_clear_pointer (&self->public_tags, g_slist_free);
524
525 if (self->highlighter == NULL)
526 IDE_EXIT;
527
528 ide_highlight_engine_queue_work (self);
529
530 IDE_EXIT;
531 }
532
533 static void
ide_highlight_engine_set_highlighter(IdeHighlightEngine * self,IdeHighlighter * highlighter)534 ide_highlight_engine_set_highlighter (IdeHighlightEngine *self,
535 IdeHighlighter *highlighter)
536 {
537 g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
538 g_return_if_fail (!highlighter || IDE_IS_HIGHLIGHTER (highlighter));
539
540 if (g_set_object (&self->highlighter, highlighter))
541 {
542 if (highlighter != NULL)
543 {
544 IDE_HIGHLIGHTER_GET_IFACE (highlighter)->set_engine (highlighter, self);
545 ide_highlighter_load (highlighter);
546 }
547
548 ide_highlight_engine_reload (self);
549 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HIGHLIGHTER]);
550 }
551 }
552
553 static void
ide_highlight_engine__buffer_insert_text_cb(IdeHighlightEngine * self,GtkTextIter * location,gchar * text,gint len,IdeBuffer * buffer)554 ide_highlight_engine__buffer_insert_text_cb (IdeHighlightEngine *self,
555 GtkTextIter *location,
556 gchar *text,
557 gint len,
558 IdeBuffer *buffer)
559 {
560 GtkTextIter begin;
561 GtkTextIter end;
562
563 IDE_ENTRY;
564
565 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
566 g_assert (location);
567 g_assert (text);
568 g_assert (IDE_IS_BUFFER (buffer));
569
570 if (!self->enabled)
571 IDE_EXIT;
572
573 /*
574 * Backward the begin iter len characters from location
575 * (location points to the end of the string) in order to get
576 * the iter position where our inserted text was started.
577 */
578 begin = *location;
579 gtk_text_iter_backward_chars (&begin, g_utf8_strlen (text, len));
580
581 end = *location;
582
583 invalidate_and_highlight (self, &begin, &end);
584
585 IDE_EXIT;
586 }
587
588 static void
ide_highlight_engine__buffer_delete_range_cb(IdeHighlightEngine * self,GtkTextIter * range_begin,GtkTextIter * range_end,IdeBuffer * buffer)589 ide_highlight_engine__buffer_delete_range_cb (IdeHighlightEngine *self,
590 GtkTextIter *range_begin,
591 GtkTextIter *range_end,
592 IdeBuffer *buffer)
593 {
594 GtkTextIter begin;
595 GtkTextIter end;
596
597 IDE_ENTRY;
598
599 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
600 g_assert (range_begin);
601 g_assert (IDE_IS_BUFFER (buffer));
602
603 if (!self->enabled)
604 IDE_EXIT;
605
606 /*
607 * No need to use the range_end since everything that
608 * was after range_end will now be after range_begin
609 */
610 begin = *range_begin;
611 end = *range_begin;
612
613 invalidate_and_highlight (self, &begin, &end);
614
615 IDE_EXIT;
616 }
617
618 static void
ide_highlight_engine__notify_language_cb(IdeHighlightEngine * self,GParamSpec * pspec,IdeBuffer * buffer)619 ide_highlight_engine__notify_language_cb (IdeHighlightEngine *self,
620 GParamSpec *pspec,
621 IdeBuffer *buffer)
622 {
623 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
624 g_assert (IDE_IS_BUFFER (buffer));
625
626 if (self->extension != NULL)
627 {
628 GtkSourceLanguage *language;
629 const gchar *lang_id = NULL;
630
631 if ((language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer))))
632 lang_id = gtk_source_language_get_id (language);
633
634 ide_extension_adapter_set_value (self->extension, lang_id);
635 }
636 }
637
638 static void
ide_highlight_engine__notify_style_scheme_cb(IdeHighlightEngine * self,GParamSpec * pspec,IdeBuffer * buffer)639 ide_highlight_engine__notify_style_scheme_cb (IdeHighlightEngine *self,
640 GParamSpec *pspec,
641 IdeBuffer *buffer)
642 {
643 GtkSourceStyleScheme *style_scheme;
644 GSList *iter;
645
646 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
647 g_assert (IDE_IS_BUFFER (buffer));
648
649 style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
650
651 for (iter = self->private_tags; iter; iter = iter->next)
652 sync_tag_style (style_scheme, iter->data);
653 for (iter = self->public_tags; iter; iter = iter->next)
654 sync_tag_style (style_scheme, iter->data);
655 }
656
657 void
ide_highlight_engine_clear(IdeHighlightEngine * self)658 ide_highlight_engine_clear (IdeHighlightEngine *self)
659 {
660 g_autoptr(GtkTextBuffer) buffer = NULL;
661 GtkTextIter begin;
662 GtkTextIter end;
663
664 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
665
666 buffer = g_weak_ref_get (&self->buffer_wref);
667
668 if (buffer != NULL)
669 {
670 gtk_text_buffer_get_bounds (buffer, &begin, &end);
671
672 for (const GSList *iter = self->public_tags; iter; iter = iter->next)
673 {
674 GtkTextTag *tag = iter->data;
675 gtk_text_buffer_remove_tag (buffer, tag, &begin, &end);
676 }
677 }
678 }
679
680 static void
ide_highlight_engine__bind_buffer_cb(IdeHighlightEngine * self,IdeBuffer * buffer,DzlSignalGroup * group)681 ide_highlight_engine__bind_buffer_cb (IdeHighlightEngine *self,
682 IdeBuffer *buffer,
683 DzlSignalGroup *group)
684 {
685 GtkTextBuffer *text_buffer = (GtkTextBuffer *)buffer;
686 GtkTextIter begin;
687 GtkTextIter end;
688
689 IDE_ENTRY;
690
691 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
692 g_assert (IDE_IS_BUFFER (buffer));
693 g_assert (DZL_IS_SIGNAL_GROUP (group));
694 g_assert (self->invalid_begin == NULL);
695 g_assert (self->invalid_end == NULL);
696
697 g_weak_ref_set (&self->buffer_wref, buffer);
698
699 gtk_text_buffer_get_bounds (text_buffer, &begin, &end);
700
701 self->invalid_begin = gtk_text_buffer_create_mark (text_buffer, NULL, &begin, TRUE);
702 self->invalid_end = gtk_text_buffer_create_mark (text_buffer, NULL, &end, FALSE);
703
704 /* We can hold a full reference to the text marks, without
705 * taking a reference to the buffer. We want to avoid a reference
706 * to the buffer for cyclic reasons.
707 */
708 g_object_ref (self->invalid_begin);
709 g_object_ref (self->invalid_end);
710
711 ide_highlight_engine__notify_style_scheme_cb (self, NULL, buffer);
712 ide_highlight_engine__notify_language_cb (self, NULL, buffer);
713
714 ide_highlight_engine_reload (self);
715
716 IDE_EXIT;
717 }
718
719 static void
ide_highlight_engine__unbind_buffer_cb(IdeHighlightEngine * self,DzlSignalGroup * group)720 ide_highlight_engine__unbind_buffer_cb (IdeHighlightEngine *self,
721 DzlSignalGroup *group)
722 {
723 g_autoptr(GtkTextBuffer) text_buffer = NULL;
724
725 IDE_ENTRY;
726
727 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
728 g_assert (DZL_IS_SIGNAL_GROUP (group));
729
730 text_buffer = g_weak_ref_get (&self->buffer_wref);
731
732 dzl_clear_source (&self->work_timeout);
733
734 if (text_buffer != NULL)
735 {
736 g_autoptr(GSList) private_tags = NULL;
737 g_autoptr(GSList) public_tags = NULL;
738 GtkTextTagTable *tag_table;
739 GtkTextIter begin;
740 GtkTextIter end;
741
742 tag_table = gtk_text_buffer_get_tag_table (text_buffer);
743
744 gtk_text_buffer_delete_mark (text_buffer, self->invalid_begin);
745 gtk_text_buffer_delete_mark (text_buffer, self->invalid_end);
746
747 gtk_text_buffer_get_bounds (text_buffer, &begin, &end);
748
749 private_tags = g_steal_pointer (&self->private_tags);
750 public_tags = g_steal_pointer (&self->public_tags);
751
752 for (const GSList *iter = private_tags; iter; iter = iter->next)
753 {
754 gtk_text_buffer_remove_tag (text_buffer, iter->data, &begin, &end);
755 gtk_text_tag_table_remove (tag_table, iter->data);
756 }
757
758 for (const GSList *iter = public_tags; iter; iter = iter->next)
759 {
760 gtk_text_buffer_remove_tag (text_buffer, iter->data, &begin, &end);
761 gtk_text_tag_table_remove (tag_table, iter->data);
762 }
763 }
764
765 g_clear_pointer (&self->public_tags, g_slist_free);
766 g_clear_pointer (&self->private_tags, g_slist_free);
767
768 g_clear_object (&self->invalid_begin);
769 g_clear_object (&self->invalid_end);
770
771 IDE_EXIT;
772 }
773
774 static void
ide_highlight_engine_set_buffer(IdeHighlightEngine * self,IdeBuffer * buffer)775 ide_highlight_engine_set_buffer (IdeHighlightEngine *self,
776 IdeBuffer *buffer)
777 {
778 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
779 g_assert (!buffer || GTK_IS_TEXT_BUFFER (buffer));
780
781 /* We can get GtkSourceBuffer intermittently here. */
782 if (!buffer || IDE_IS_BUFFER (buffer))
783 {
784 dzl_signal_group_set_target (self->signal_group, buffer);
785 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUFFER]);
786 }
787 }
788
789 static void
ide_highlight_engine_settings_changed(IdeHighlightEngine * self,const gchar * key,GSettings * settings)790 ide_highlight_engine_settings_changed (IdeHighlightEngine *self,
791 const gchar *key,
792 GSettings *settings)
793 {
794 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
795 g_assert (G_IS_SETTINGS (settings));
796
797 if (g_settings_get_boolean (settings, "semantic-highlighting"))
798 {
799 self->enabled = TRUE;
800 ide_highlight_engine_rebuild (self);
801 }
802 else
803 {
804 self->enabled = FALSE;
805 ide_highlight_engine_clear (self);
806 }
807 }
808
809 static void
ide_highlight_engine__notify_extension(IdeHighlightEngine * self,GParamSpec * pspec,IdeExtensionAdapter * adapter)810 ide_highlight_engine__notify_extension (IdeHighlightEngine *self,
811 GParamSpec *pspec,
812 IdeExtensionAdapter *adapter)
813 {
814 IdeHighlighter *highlighter;
815
816 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
817 g_assert (IDE_IS_EXTENSION_ADAPTER (adapter));
818
819 highlighter = ide_extension_adapter_get_extension (adapter);
820 g_return_if_fail (!highlighter || IDE_IS_HIGHLIGHTER (highlighter));
821
822 ide_highlight_engine_set_highlighter (self, highlighter);
823 }
824
825 static void
ide_highlight_engine_parent_set(IdeObject * object,IdeObject * parent)826 ide_highlight_engine_parent_set (IdeObject *object,
827 IdeObject *parent)
828 {
829 IdeHighlightEngine *self = (IdeHighlightEngine *)object;
830
831 g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
832 g_assert (!parent || IDE_IS_OBJECT (parent));
833
834 if (parent == NULL)
835 {
836 g_clear_object (&self->extension);
837 return;
838 }
839
840 self->extension = ide_extension_adapter_new (IDE_OBJECT (self),
841 NULL,
842 IDE_TYPE_HIGHLIGHTER,
843 "Highlighter-Languages",
844 NULL);
845 g_signal_connect_object (self->extension,
846 "notify::extension",
847 G_CALLBACK (ide_highlight_engine__notify_extension),
848 self,
849 G_CONNECT_SWAPPED);
850 }
851
852 static void
ide_highlight_engine_dispose(GObject * object)853 ide_highlight_engine_dispose (GObject *object)
854 {
855 IdeHighlightEngine *self = (IdeHighlightEngine *)object;
856
857 g_weak_ref_set (&self->buffer_wref, NULL);
858 g_clear_object (&self->signal_group);
859 g_clear_object (&self->extension);
860 g_clear_object (&self->highlighter);
861 g_clear_object (&self->settings);
862
863 G_OBJECT_CLASS (ide_highlight_engine_parent_class)->dispose (object);
864 }
865
866 static void
ide_highlight_engine_finalize(GObject * object)867 ide_highlight_engine_finalize (GObject *object)
868 {
869 IdeHighlightEngine *self = (IdeHighlightEngine *)object;
870
871 g_weak_ref_clear (&self->buffer_wref);
872
873 G_OBJECT_CLASS (ide_highlight_engine_parent_class)->finalize (object);
874 }
875
876 static void
ide_highlight_engine_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)877 ide_highlight_engine_get_property (GObject *object,
878 guint prop_id,
879 GValue *value,
880 GParamSpec *pspec)
881 {
882 IdeHighlightEngine *self = IDE_HIGHLIGHT_ENGINE (object);
883
884 switch (prop_id)
885 {
886 case PROP_BUFFER:
887 g_value_set_object (value, ide_highlight_engine_get_buffer (self));
888 break;
889
890 case PROP_HIGHLIGHTER:
891 g_value_set_object (value, ide_highlight_engine_get_highlighter (self));
892 break;
893
894 default:
895 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
896 }
897 }
898
899 static void
ide_highlight_engine_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)900 ide_highlight_engine_set_property (GObject *object,
901 guint prop_id,
902 const GValue *value,
903 GParamSpec *pspec)
904 {
905 IdeHighlightEngine *self = IDE_HIGHLIGHT_ENGINE (object);
906
907 switch (prop_id)
908 {
909 case PROP_BUFFER:
910 ide_highlight_engine_set_buffer (self, g_value_get_object (value));
911 break;
912
913 default:
914 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
915 }
916 }
917
918 static void
ide_highlight_engine_class_init(IdeHighlightEngineClass * klass)919 ide_highlight_engine_class_init (IdeHighlightEngineClass *klass)
920 {
921 GObjectClass *object_class = G_OBJECT_CLASS (klass);
922 IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
923
924 object_class->dispose = ide_highlight_engine_dispose;
925 object_class->finalize = ide_highlight_engine_finalize;
926 object_class->get_property = ide_highlight_engine_get_property;
927 object_class->set_property = ide_highlight_engine_set_property;
928
929 i_object_class->parent_set = ide_highlight_engine_parent_set;
930
931 properties [PROP_BUFFER] =
932 g_param_spec_object ("buffer",
933 "Buffer",
934 "The buffer to highlight.",
935 IDE_TYPE_BUFFER,
936 (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
937
938 properties [PROP_HIGHLIGHTER] =
939 g_param_spec_object ("highlighter",
940 "Highlighter",
941 "The highlighter to use for type information.",
942 IDE_TYPE_HIGHLIGHTER,
943 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
944
945 g_object_class_install_properties (object_class, LAST_PROP, properties);
946 }
947
948 static void
ide_highlight_engine_init(IdeHighlightEngine * self)949 ide_highlight_engine_init (IdeHighlightEngine *self)
950 {
951 g_weak_ref_init (&self->buffer_wref, NULL);
952
953 self->settings = g_settings_new ("org.gnome.builder.code-insight");
954 self->enabled = g_settings_get_boolean (self->settings, "semantic-highlighting");
955 self->signal_group = dzl_signal_group_new (IDE_TYPE_BUFFER);
956
957 dzl_signal_group_connect_object (self->signal_group,
958 "insert-text",
959 G_CALLBACK (ide_highlight_engine__buffer_insert_text_cb),
960 self,
961 G_CONNECT_SWAPPED | G_CONNECT_AFTER);
962
963 dzl_signal_group_connect_object (self->signal_group,
964 "delete-range",
965 G_CALLBACK (ide_highlight_engine__buffer_delete_range_cb),
966 self,
967 G_CONNECT_SWAPPED | G_CONNECT_AFTER);
968
969 dzl_signal_group_connect_object (self->signal_group,
970 "notify::language",
971 G_CALLBACK (ide_highlight_engine__notify_language_cb),
972 self,
973 G_CONNECT_SWAPPED);
974
975 dzl_signal_group_connect_object (self->signal_group,
976 "notify::style-scheme",
977 G_CALLBACK (ide_highlight_engine__notify_style_scheme_cb),
978 self,
979 G_CONNECT_SWAPPED);
980
981 g_signal_connect_object (self->signal_group,
982 "bind",
983 G_CALLBACK (ide_highlight_engine__bind_buffer_cb),
984 self,
985 G_CONNECT_SWAPPED);
986
987 g_signal_connect_object (self->signal_group,
988 "unbind",
989 G_CALLBACK (ide_highlight_engine__unbind_buffer_cb),
990 self,
991 G_CONNECT_SWAPPED);
992
993 g_signal_connect_object (self->settings,
994 "changed::semantic-highlighting",
995 G_CALLBACK (ide_highlight_engine_settings_changed),
996 self,
997 G_CONNECT_SWAPPED);
998 }
999
1000 IdeHighlightEngine *
ide_highlight_engine_new(IdeBuffer * buffer)1001 ide_highlight_engine_new (IdeBuffer *buffer)
1002 {
1003 IdeHighlightEngine *self;
1004 IdeObjectBox *box;
1005
1006 g_return_val_if_fail (IDE_IS_BUFFER (buffer), NULL);
1007
1008 self = g_object_new (IDE_TYPE_HIGHLIGHT_ENGINE,
1009 "buffer", buffer,
1010 NULL);
1011
1012 box = ide_object_box_from_object (G_OBJECT (buffer));
1013 ide_object_append (IDE_OBJECT (box), IDE_OBJECT (self));
1014
1015 return g_steal_pointer (&self);
1016 }
1017
1018 /**
1019 * ide_highlight_engine_get_highlighter:
1020 * @self: an #IdeHighlightEngine.
1021 *
1022 * Gets the IdeHighlightEngine:highlighter property.
1023 *
1024 * Returns: (transfer none): An #IdeHighlighter.
1025 *
1026 * Since: 3.32
1027 */
1028 IdeHighlighter *
ide_highlight_engine_get_highlighter(IdeHighlightEngine * self)1029 ide_highlight_engine_get_highlighter (IdeHighlightEngine *self)
1030 {
1031 g_return_val_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self), NULL);
1032
1033 return self->highlighter;
1034 }
1035
1036 /**
1037 * ide_highlight_engine_get_buffer:
1038 * @self: an #IdeHighlightEngine.
1039 *
1040 * Gets the IdeHighlightEngine:buffer property.
1041 *
1042 * Returns: (transfer none): An #IdeBuffer.
1043 *
1044 * Since: 3.32
1045 */
1046 IdeBuffer *
ide_highlight_engine_get_buffer(IdeHighlightEngine * self)1047 ide_highlight_engine_get_buffer (IdeHighlightEngine *self)
1048 {
1049 g_autoptr(IdeBuffer) buffer = NULL;
1050
1051 g_return_val_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self), NULL);
1052
1053 /* We don't need the "thread-safety" provided by GWeakRef here,
1054 * (where it gives us a new object reference). It is safe to
1055 * return a borrowed reference instead.
1056 */
1057 buffer = g_weak_ref_get (&self->buffer_wref);
1058 return buffer;
1059 }
1060
1061 void
ide_highlight_engine_rebuild(IdeHighlightEngine * self)1062 ide_highlight_engine_rebuild (IdeHighlightEngine *self)
1063 {
1064 g_autoptr(GtkTextBuffer) buffer = NULL;
1065
1066 IDE_ENTRY;
1067
1068 g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
1069
1070 buffer = g_weak_ref_get (&self->buffer_wref);
1071
1072 if (buffer != NULL)
1073 {
1074 GtkTextIter begin;
1075 GtkTextIter end;
1076
1077 gtk_text_buffer_get_bounds (buffer, &begin, &end);
1078 gtk_text_buffer_move_mark (buffer, self->invalid_begin, &begin);
1079 gtk_text_buffer_move_mark (buffer, self->invalid_end, &end);
1080
1081 ide_highlight_engine_queue_work (self);
1082 }
1083
1084 IDE_EXIT;
1085 }
1086
1087 /**
1088 * ide_highlight_engine_invalidate:
1089 * @self: An #IdeHighlightEngine.
1090 * @begin: the beginning of the range to invalidate
1091 * @end: the end of the range to invalidate
1092 *
1093 * This function will extend the invalidated range of the buffer to include
1094 * the range of @begin to @end.
1095 *
1096 * The highlighter will be queued to interactively update the invalidated
1097 * region.
1098 *
1099 * Updating the invalidated region of the buffer may take some time, as it is
1100 * important that the highlighter does not block for more than 1-2 milliseconds
1101 * to avoid dropping frames.
1102 *
1103 * Since: 3.32
1104 */
1105 void
ide_highlight_engine_invalidate(IdeHighlightEngine * self,const GtkTextIter * begin,const GtkTextIter * end)1106 ide_highlight_engine_invalidate (IdeHighlightEngine *self,
1107 const GtkTextIter *begin,
1108 const GtkTextIter *end)
1109 {
1110 GtkTextBuffer *buffer;
1111 GtkTextIter mark_begin;
1112 GtkTextIter mark_end;
1113
1114 IDE_ENTRY;
1115
1116 g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
1117 g_return_if_fail (begin != NULL);
1118 g_return_if_fail (end != NULL);
1119
1120 buffer = gtk_text_iter_get_buffer (begin);
1121
1122 gtk_text_buffer_get_iter_at_mark (buffer, &mark_begin, self->invalid_begin);
1123 gtk_text_buffer_get_iter_at_mark (buffer, &mark_end, self->invalid_end);
1124
1125 if (gtk_text_iter_equal (&mark_begin, &mark_end))
1126 {
1127 gtk_text_buffer_move_mark (buffer, self->invalid_begin, begin);
1128 gtk_text_buffer_move_mark (buffer, self->invalid_end, end);
1129 }
1130 else
1131 {
1132 if (gtk_text_iter_compare (begin, &mark_begin) < 0)
1133 gtk_text_buffer_move_mark (buffer, self->invalid_begin, begin);
1134 if (gtk_text_iter_compare (end, &mark_end) > 0)
1135 gtk_text_buffer_move_mark (buffer, self->invalid_end, end);
1136 }
1137
1138 ide_highlight_engine_queue_work (self);
1139
1140 IDE_EXIT;
1141 }
1142
1143 /**
1144 * ide_highlight_engine_get_style:
1145 * @self: the #IdeHighlightEngine
1146 * @style_name: the name of the style to retrieve
1147 *
1148 * A #GtkTextTag for @style_name.
1149 *
1150 * Returns: (transfer none): a #GtkTextTag.
1151 *
1152 * Since: 3.32
1153 */
1154 GtkTextTag *
ide_highlight_engine_get_style(IdeHighlightEngine * self,const gchar * style_name)1155 ide_highlight_engine_get_style (IdeHighlightEngine *self,
1156 const gchar *style_name)
1157 {
1158 return get_tag_from_style (self, style_name, FALSE);
1159 }
1160
1161 void
ide_highlight_engine_pause(IdeHighlightEngine * self)1162 ide_highlight_engine_pause (IdeHighlightEngine *self)
1163 {
1164 g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
1165
1166 dzl_signal_group_block (self->signal_group);
1167 }
1168
1169 void
ide_highlight_engine_unpause(IdeHighlightEngine * self)1170 ide_highlight_engine_unpause (IdeHighlightEngine *self)
1171 {
1172 g_autoptr(IdeBuffer) buffer = NULL;
1173
1174 g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
1175 g_return_if_fail (self->signal_group != NULL);
1176
1177 dzl_signal_group_unblock (self->signal_group);
1178
1179 buffer = g_weak_ref_get (&self->buffer_wref);
1180
1181 if (buffer != NULL)
1182 {
1183 /* Notify of some blocked signals */
1184 ide_highlight_engine__notify_style_scheme_cb (self, NULL, buffer);
1185 ide_highlight_engine__notify_language_cb (self, NULL, buffer);
1186
1187 ide_highlight_engine_reload (self);
1188 }
1189 }
1190