1 /* ide-diagnostics-manager.c
2 *
3 * Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
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-diagnostics-manager"
22
23 #include "config.h"
24
25 #include <gtksourceview/gtksource.h>
26 #include <libide-plugins.h>
27
28 #include "ide-buffer.h"
29 #include "ide-buffer-manager.h"
30 #include "ide-buffer-private.h"
31 #include "ide-diagnostic.h"
32 #include "ide-diagnostic-provider.h"
33 #include "ide-diagnostics.h"
34 #include "ide-diagnostics-manager.h"
35 #include "ide-diagnostics-manager-private.h"
36
37 #define DEFAULT_DIAGNOSE_DELAY 333
38 #define DIAG_GROUP_MAGIC 0xF1282727
39 #define IS_DIAGNOSTICS_GROUP(g) ((g) && (g)->magic == DIAG_GROUP_MAGIC)
40
41 typedef struct
42 {
43 /*
44 * Used to give ourself a modicum of assurance our structure hasn't
45 * been miss-used.
46 */
47 guint magic;
48
49 /*
50 * This is our identifier for the diagnostics. We use this as the key in
51 * the hash table so that we can quickly find the target buffer. If the
52 * IdeBuffer:file property changes, we will have to fallback to the
53 * buffer to clear old entries.
54 */
55 GFile *file;
56
57 /*
58 * This hash table uses the given provider as the key and the last
59 * reported IdeDiagnostics as the value.
60 */
61 GHashTable *diagnostics_by_provider;
62
63 /*
64 * This extension set adapter is used to update the providers that are
65 * available based on the buffers current language. They may change
66 * at runtime due to the buffers language changing. When that happens
67 * we purge items from @diagnostics_by_provider and queue a diagnose
68 * request of the new provider.
69 */
70 IdeExtensionSetAdapter *adapter;
71
72 /* The most recent bytes we received for a future diagnosis. */
73 GBytes *contents;
74
75 /* The last language id we were notified about */
76 const gchar *lang_id;
77
78 /*
79 * This is our sequence number for diagnostics. It is monotonically
80 * increasing with every diagnostic discovered.
81 */
82 guint sequence;
83
84 /*
85 * If we are currently diagnosing, then this will be set to a
86 * number greater than zero.
87 */
88 guint in_diagnose;
89
90 /*
91 * If we need a diagnose this bit will be set. If we complete a
92 * diagnosis and this bit is set, then we will automatically queue
93 * another diagnose upon completion.
94 */
95 guint needs_diagnose : 1;
96
97 /*
98 * This bit is set if we know the file or buffer has diagnostics. This
99 * is useful when we've cleaned up our extensions and no longer have
100 * the diagnostics loaded in memory, but we know that it previously
101 * had diagnostics which have not been rectified.
102 */
103 guint has_diagnostics : 1;
104
105 /*
106 * This bit is set when the group has been removed from the
107 * IdeDiagnosticsManager. That allows the providers to cleanup
108 * as necessary when their async operations complete.
109 */
110 guint was_removed : 1;
111
112 } IdeDiagnosticsGroup;
113
114 struct _IdeDiagnosticsManager
115 {
116 IdeObject parent_instance;
117
118 /*
119 * This hashtable contains a mapping of GFile to the IdeDiagnosticsGroup
120 * for the file. When a buffer is renamed (the IdeBuffer:file property
121 * is changed) we need to update this entry so it reflects the new
122 * location.
123 */
124 GHashTable *groups_by_file;
125
126 /*
127 * If any group has a queued diagnose in process, this will be set so
128 * we can coalesce the dispatch of everything at the same time.
129 */
130 guint queued_diagnose_source;
131 };
132
133 enum {
134 PROP_0,
135 PROP_BUSY,
136 N_PROPS
137 };
138
139 enum {
140 CHANGED,
141 N_SIGNALS
142 };
143
144
145 static gboolean ide_diagnostics_manager_clear_by_provider (IdeDiagnosticsManager *self,
146 IdeDiagnosticProvider *provider);
147 static void ide_diagnostics_manager_add_diagnostic (IdeDiagnosticsManager *self,
148 IdeDiagnosticProvider *provider,
149 IdeDiagnostic *diagnostic);
150 static void ide_diagnostics_group_queue_diagnose (IdeDiagnosticsGroup *group,
151 IdeDiagnosticsManager *self);
152
153
154 static GParamSpec *properties [N_PROPS];
155 static guint signals [N_SIGNALS];
156
G_DEFINE_FINAL_TYPE(IdeDiagnosticsManager,ide_diagnostics_manager,IDE_TYPE_OBJECT)157 G_DEFINE_FINAL_TYPE (IdeDiagnosticsManager, ide_diagnostics_manager, IDE_TYPE_OBJECT)
158
159 static void
160 free_diagnostics (gpointer data)
161 {
162 IdeDiagnostics *diagnostics = data;
163
164 g_clear_object (&diagnostics);
165 }
166
167 static inline guint
diagnostics_get_size(IdeDiagnostics * diags)168 diagnostics_get_size (IdeDiagnostics *diags)
169 {
170 return diags ? g_list_model_get_n_items (G_LIST_MODEL (diags)) : 0;
171 }
172
173 static void
ide_diagnostics_group_finalize(IdeDiagnosticsGroup * group)174 ide_diagnostics_group_finalize (IdeDiagnosticsGroup *group)
175 {
176 g_assert (IDE_IS_MAIN_THREAD ());
177 g_assert (group != NULL);
178 g_assert (IS_DIAGNOSTICS_GROUP (group));
179
180 group->magic = 0;
181
182 g_clear_pointer (&group->diagnostics_by_provider, g_hash_table_unref);
183 g_clear_pointer (&group->contents, g_bytes_unref);
184 ide_clear_and_destroy_object (&group->adapter);
185 g_clear_object (&group->file);
186 }
187
188 static IdeDiagnosticsGroup *
ide_diagnostics_group_new(GFile * file)189 ide_diagnostics_group_new (GFile *file)
190 {
191 IdeDiagnosticsGroup *group;
192
193 g_assert (IDE_IS_MAIN_THREAD ());
194 g_assert (G_IS_FILE (file));
195
196 group = g_rc_box_new0 (IdeDiagnosticsGroup);
197 group->magic = DIAG_GROUP_MAGIC;
198 group->file = g_object_ref (file);
199
200 return group;
201 }
202
203 static IdeDiagnosticsGroup *
ide_diagnostics_group_ref(IdeDiagnosticsGroup * group)204 ide_diagnostics_group_ref (IdeDiagnosticsGroup *group)
205 {
206 g_assert (IDE_IS_MAIN_THREAD ());
207 g_assert (group != NULL);
208 g_assert (IS_DIAGNOSTICS_GROUP (group));
209
210 return g_rc_box_acquire (group);
211 }
212
213 static void
ide_diagnostics_group_unref(IdeDiagnosticsGroup * group)214 ide_diagnostics_group_unref (IdeDiagnosticsGroup *group)
215 {
216 g_assert (IDE_IS_MAIN_THREAD ());
217 g_assert (group != NULL);
218 g_assert (IS_DIAGNOSTICS_GROUP (group));
219
220 g_rc_box_release_full (group, (GDestroyNotify)ide_diagnostics_group_finalize);
221 }
222
223 static guint
ide_diagnostics_group_has_diagnostics(IdeDiagnosticsGroup * group)224 ide_diagnostics_group_has_diagnostics (IdeDiagnosticsGroup *group)
225 {
226 g_assert (IDE_IS_MAIN_THREAD ());
227 g_assert (group != NULL);
228 g_assert (IS_DIAGNOSTICS_GROUP (group));
229
230 if (group->diagnostics_by_provider != NULL)
231 {
232 GHashTableIter iter;
233 gpointer value;
234
235 g_hash_table_iter_init (&iter, group->diagnostics_by_provider);
236
237 while (g_hash_table_iter_next (&iter, NULL, &value))
238 {
239 IdeDiagnostics *diagnostics = value;
240
241 if (diagnostics_get_size (diagnostics) > 0)
242 return TRUE;
243 }
244 }
245
246 return FALSE;
247 }
248
249 static gboolean
ide_diagnostics_group_can_dispose(IdeDiagnosticsGroup * group)250 ide_diagnostics_group_can_dispose (IdeDiagnosticsGroup *group)
251 {
252 g_assert (IDE_IS_MAIN_THREAD ());
253 g_assert (group != NULL);
254 g_assert (IS_DIAGNOSTICS_GROUP (group));
255
256 /*
257 * We can cleanup this group if we don't have a buffer loaded and
258 * the adapters have been unloaded and there are no diagnostics
259 * registered for the group.
260 */
261
262 return group->adapter == NULL &&
263 group->has_diagnostics == FALSE;
264 }
265
266 static void
ide_diagnostics_group_add(IdeDiagnosticsGroup * group,IdeDiagnosticProvider * provider,IdeDiagnostic * diagnostic)267 ide_diagnostics_group_add (IdeDiagnosticsGroup *group,
268 IdeDiagnosticProvider *provider,
269 IdeDiagnostic *diagnostic)
270 {
271 IdeDiagnostics *diagnostics;
272
273 g_assert (IDE_IS_MAIN_THREAD ());
274 g_assert (group != NULL);
275 g_assert (IS_DIAGNOSTICS_GROUP (group));
276 g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
277 g_assert (diagnostic != NULL);
278
279 if (group->diagnostics_by_provider == NULL)
280 group->diagnostics_by_provider = g_hash_table_new_full (NULL, NULL, NULL, free_diagnostics);
281
282 diagnostics = g_hash_table_lookup (group->diagnostics_by_provider, provider);
283
284 if (diagnostics == NULL)
285 {
286 diagnostics = ide_diagnostics_new ();
287 g_hash_table_insert (group->diagnostics_by_provider, provider, diagnostics);
288 }
289
290 ide_diagnostics_add (diagnostics, diagnostic);
291
292 group->has_diagnostics = TRUE;
293 group->sequence++;
294 }
295
296 static void
ide_diagnostics_group_diagnose_cb(GObject * object,GAsyncResult * result,gpointer user_data)297 ide_diagnostics_group_diagnose_cb (GObject *object,
298 GAsyncResult *result,
299 gpointer user_data)
300 {
301 IdeDiagnosticProvider *provider = (IdeDiagnosticProvider *)object;
302 g_autoptr(IdeDiagnosticsManager) self = user_data;
303 g_autoptr(IdeDiagnostics) diagnostics = NULL;
304 g_autoptr(GError) error = NULL;
305 IdeDiagnosticsGroup *group;
306 gboolean changed;
307
308 IDE_ENTRY;
309
310 g_assert (IDE_IS_MAIN_THREAD ());
311 g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
312 g_assert (G_IS_ASYNC_RESULT (result));
313 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
314
315 IDE_TRACE_MSG ("%s diagnosis completed", G_OBJECT_TYPE_NAME (provider));
316
317 diagnostics = ide_diagnostic_provider_diagnose_finish (provider, result, &error);
318
319 if (error != NULL &&
320 !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
321 !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
322 g_debug ("%s", error->message);
323
324 /*
325 * This fetches the group our provider belongs to. Since the group is
326 * reference counted (and we only release it when our provider is
327 * finalized), we should be guaranteed we have a valid group.
328 */
329 group = g_object_get_data (G_OBJECT (provider), "IDE_DIAGNOSTICS_GROUP");
330
331 if (group == NULL)
332 {
333 /* Warning and bail if we failed to get the diagnostic group.
334 * This shouldn't be happening, but I have definitely seen it
335 * so it is probably related to disposal.
336 */
337 g_warning ("Failed to locate group, possibly disposed.");
338 IDE_EXIT;
339 }
340
341 g_assert (IS_DIAGNOSTICS_GROUP (group));
342
343 /*
344 * Clear all of our old diagnostics no matter where they ended up.
345 */
346 changed = ide_diagnostics_manager_clear_by_provider (self, provider);
347
348 /*
349 * The following adds diagnostics to the appropriate group, but tries the
350 * group we belong to first as our fast path. That will almost always be
351 * the case, except when a diagnostic came up for a header or something
352 * while parsing a given file.
353 */
354 if (diagnostics != NULL)
355 {
356 guint length = diagnostics_get_size (diagnostics);
357
358 for (guint i = 0; i < length; i++)
359 {
360 g_autoptr(IdeDiagnostic) diagnostic = g_list_model_get_item (G_LIST_MODEL (diagnostics), i);
361 GFile *file = ide_diagnostic_get_file (diagnostic);
362
363 if (file != NULL)
364 {
365 if (g_file_equal (file, group->file))
366 ide_diagnostics_group_add (group, provider, diagnostic);
367 else
368 ide_diagnostics_manager_add_diagnostic (self, provider, diagnostic);
369 }
370 }
371
372 if (length > 0)
373 changed = TRUE;
374 }
375
376 group->in_diagnose--;
377
378 /*
379 * Ensure we increment our sequence number even when no diagnostics were
380 * reported. This ensures that the gutter gets cleared and line-flags
381 * cache updated.
382 */
383 group->sequence++;
384
385 /*
386 * Since the individual groups have sequence numbers associated with changes,
387 * it's okay to emit this for every provider completion. That allows the UIs
388 * to update faster as each provider completes at the expensive of a little
389 * more CPU activity.
390 */
391 if (changed)
392 g_signal_emit (self, signals [CHANGED], 0);
393
394 /*
395 * If there are no more diagnostics providers active and the group needs
396 * another diagnosis, then we can start the next one now.
397 *
398 * If we are completing this diagnosis and the buffer was already released
399 * (and other diagnose providers have unloaded), we might be able to clean
400 * up the group and be done with things.
401 */
402 if (group->was_removed == FALSE && group->in_diagnose == 0 && group->needs_diagnose)
403 {
404 ide_diagnostics_group_queue_diagnose (group, self);
405 }
406 else if (ide_diagnostics_group_can_dispose (group))
407 {
408 group->was_removed = TRUE;
409 g_hash_table_remove (self->groups_by_file, group->file);
410 IDE_EXIT;
411 }
412
413 IDE_EXIT;
414 }
415
416 static void
ide_diagnostics_group_diagnose_foreach(IdeExtensionSetAdapter * adapter,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)417 ide_diagnostics_group_diagnose_foreach (IdeExtensionSetAdapter *adapter,
418 PeasPluginInfo *plugin_info,
419 PeasExtension *exten,
420 gpointer user_data)
421 {
422 IdeDiagnosticProvider *provider = (IdeDiagnosticProvider *)exten;
423 IdeDiagnosticsManager *self = user_data;
424 IdeDiagnosticsGroup *group;
425
426 IDE_ENTRY;
427
428 g_assert (IDE_IS_MAIN_THREAD ());
429 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
430 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
431 g_assert (plugin_info != NULL);
432 g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
433
434 group = g_object_get_data (G_OBJECT (provider), "IDE_DIAGNOSTICS_GROUP");
435
436 g_assert (group != NULL);
437 g_assert (IS_DIAGNOSTICS_GROUP (group));
438
439 group->in_diagnose++;
440
441 #ifdef IDE_ENABLE_TRACE
442 {
443 g_autofree gchar *uri = g_file_get_uri (group->file);
444 IDE_TRACE_MSG ("Beginning diagnose on %s with provider %s",
445 uri, G_OBJECT_TYPE_NAME (provider));
446 }
447 #endif
448
449 ide_diagnostic_provider_diagnose_async (provider,
450 group->file,
451 group->contents,
452 group->lang_id,
453 NULL,
454 ide_diagnostics_group_diagnose_cb,
455 g_object_ref (self));
456
457 IDE_EXIT;
458 }
459
460 static void
ide_diagnostics_group_diagnose(IdeDiagnosticsGroup * group,IdeDiagnosticsManager * self)461 ide_diagnostics_group_diagnose (IdeDiagnosticsGroup *group,
462 IdeDiagnosticsManager *self)
463 {
464 IDE_ENTRY;
465
466 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
467 g_assert (group != NULL);
468 g_assert (group->in_diagnose == FALSE);
469 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (group->adapter));
470
471 group->needs_diagnose = FALSE;
472 group->has_diagnostics = FALSE;
473
474 if (group->contents == NULL)
475 group->contents = g_bytes_new ("", 0);
476
477 ide_extension_set_adapter_foreach (group->adapter,
478 ide_diagnostics_group_diagnose_foreach,
479 self);
480
481 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
482
483 IDE_EXIT;
484 }
485
486 static gboolean
ide_diagnostics_manager_begin_diagnose(gpointer data)487 ide_diagnostics_manager_begin_diagnose (gpointer data)
488 {
489 IdeDiagnosticsManager *self = data;
490 GHashTableIter iter;
491 gpointer value;
492
493 IDE_ENTRY;
494
495 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
496
497 self->queued_diagnose_source = 0;
498
499 g_hash_table_iter_init (&iter, self->groups_by_file);
500
501 while (g_hash_table_iter_next (&iter, NULL, &value))
502 {
503 IdeDiagnosticsGroup *group = value;
504
505 g_assert (group != NULL);
506 g_assert (IS_DIAGNOSTICS_GROUP (group));
507
508 if (group->needs_diagnose && group->adapter != NULL && group->in_diagnose == 0)
509 ide_diagnostics_group_diagnose (group, self);
510 }
511
512 IDE_RETURN (G_SOURCE_REMOVE);
513 }
514
515 static void
ide_diagnostics_group_queue_diagnose(IdeDiagnosticsGroup * group,IdeDiagnosticsManager * self)516 ide_diagnostics_group_queue_diagnose (IdeDiagnosticsGroup *group,
517 IdeDiagnosticsManager *self)
518 {
519 g_assert (group != NULL);
520 g_assert (IS_DIAGNOSTICS_GROUP (group));
521 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
522
523 /*
524 * This checks to see if we are diagnosing and if not queues a diagnose.
525 * If a diagnosis is already running, we don't need to do anything now
526 * because the completion of the diagnose will tick off the next diagnose
527 * upon seening group->needs_diagnose==TRUE.
528 */
529
530 group->needs_diagnose = TRUE;
531
532 if (group->in_diagnose == 0 && self->queued_diagnose_source == 0)
533 self->queued_diagnose_source = g_timeout_add_full (G_PRIORITY_LOW,
534 DEFAULT_DIAGNOSE_DELAY,
535 ide_diagnostics_manager_begin_diagnose,
536 self, NULL);
537 }
538
539 static void
ide_diagnostics_manager_finalize(GObject * object)540 ide_diagnostics_manager_finalize (GObject *object)
541 {
542 IdeDiagnosticsManager *self = (IdeDiagnosticsManager *)object;
543
544 g_clear_handle_id (&self->queued_diagnose_source, g_source_remove);
545 g_clear_pointer (&self->groups_by_file, g_hash_table_unref);
546
547 G_OBJECT_CLASS (ide_diagnostics_manager_parent_class)->finalize (object);
548 }
549
550 static void
ide_diagnostics_manager_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)551 ide_diagnostics_manager_get_property (GObject *object,
552 guint prop_id,
553 GValue *value,
554 GParamSpec *pspec)
555 {
556 IdeDiagnosticsManager *self = (IdeDiagnosticsManager *)object;
557
558 switch (prop_id)
559 {
560 case PROP_BUSY:
561 g_value_set_boolean (value, ide_diagnostics_manager_get_busy (self));
562 break;
563
564 default:
565 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
566 }
567 }
568
569 static void
ide_diagnostics_manager_class_init(IdeDiagnosticsManagerClass * klass)570 ide_diagnostics_manager_class_init (IdeDiagnosticsManagerClass *klass)
571 {
572 GObjectClass *object_class = G_OBJECT_CLASS (klass);
573
574 object_class->finalize = ide_diagnostics_manager_finalize;
575 object_class->get_property = ide_diagnostics_manager_get_property;
576
577 properties [PROP_BUSY] =
578 g_param_spec_boolean ("busy",
579 "Busy",
580 "If the diagnostics manager is busy",
581 FALSE,
582 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
583
584 g_object_class_install_properties (object_class, N_PROPS, properties);
585
586 /**
587 * IdeDiagnosticsManager::changed:
588 * @self: an #IdeDiagnosticsManager
589 *
590 * This signal is emitted when the diagnostics have changed for any
591 * file managed by the IdeDiagnosticsManager.
592 *
593 * Since: 3.32
594 */
595 signals [CHANGED] =
596 g_signal_new ("changed",
597 G_TYPE_FROM_CLASS (klass),
598 G_SIGNAL_RUN_LAST,
599 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
600 }
601
602 static void
ide_diagnostics_manager_init(IdeDiagnosticsManager * self)603 ide_diagnostics_manager_init (IdeDiagnosticsManager *self)
604 {
605 self->groups_by_file = g_hash_table_new_full (g_file_hash,
606 (GEqualFunc)g_file_equal,
607 NULL,
608 (GDestroyNotify)ide_diagnostics_group_unref);
609 }
610
611 static void
ide_diagnostics_manager_add_diagnostic(IdeDiagnosticsManager * self,IdeDiagnosticProvider * provider,IdeDiagnostic * diagnostic)612 ide_diagnostics_manager_add_diagnostic (IdeDiagnosticsManager *self,
613 IdeDiagnosticProvider *provider,
614 IdeDiagnostic *diagnostic)
615 {
616 IdeDiagnosticsGroup *group;
617 GFile *file;
618
619 g_assert (IDE_IS_MAIN_THREAD ());
620 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
621 g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
622 g_assert (diagnostic != NULL);
623
624 /*
625 * This is our slow path for adding a diagnostic to the system. We have
626 * to locate the proper group for the diagnostic and then insert it
627 * into that group.
628 */
629
630 if (NULL == (file = ide_diagnostic_get_file (diagnostic)))
631 return;
632
633 group = g_hash_table_lookup (self->groups_by_file, file);
634
635 if (group == NULL)
636 {
637 group = ide_diagnostics_group_new (file);
638 g_hash_table_insert (self->groups_by_file, group->file, group);
639 }
640
641 g_assert (group != NULL);
642 g_assert (IS_DIAGNOSTICS_GROUP (group));
643
644 ide_diagnostics_group_add (group, provider, diagnostic);
645 }
646
647 static IdeDiagnosticsGroup *
ide_diagnostics_manager_find_group(IdeDiagnosticsManager * self,GFile * file)648 ide_diagnostics_manager_find_group (IdeDiagnosticsManager *self,
649 GFile *file)
650 {
651 IdeDiagnosticsGroup *group;
652
653 g_assert (IDE_IS_MAIN_THREAD ());
654 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
655 g_assert (G_IS_FILE (file));
656
657 if (!(group = g_hash_table_lookup (self->groups_by_file, file)))
658 {
659 group = ide_diagnostics_group_new (file);
660 g_hash_table_insert (self->groups_by_file, group->file, group);
661 }
662
663 g_assert (group != NULL);
664 g_assert (IS_DIAGNOSTICS_GROUP (group));
665
666 return group;
667 }
668
669 static IdeDiagnosticsGroup *
ide_diagnostics_manager_find_group_from_adapter(IdeDiagnosticsManager * self,IdeExtensionSetAdapter * adapter)670 ide_diagnostics_manager_find_group_from_adapter (IdeDiagnosticsManager *self,
671 IdeExtensionSetAdapter *adapter)
672 {
673 GHashTableIter iter;
674 gpointer value;
675
676 g_assert (IDE_IS_MAIN_THREAD ());
677 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
678 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
679
680 g_hash_table_iter_init (&iter, self->groups_by_file);
681
682 while (g_hash_table_iter_next (&iter, NULL, &value))
683 {
684 IdeDiagnosticsGroup *group = value;
685
686 g_assert (group != NULL);
687 g_assert (IS_DIAGNOSTICS_GROUP (group));
688
689 if (group->adapter == adapter)
690 return group;
691 }
692
693 g_assert_not_reached ();
694
695 return NULL;
696 }
697
698 static void
ide_diagnostics_manager_provider_invalidated(IdeDiagnosticsManager * self,IdeDiagnosticProvider * provider)699 ide_diagnostics_manager_provider_invalidated (IdeDiagnosticsManager *self,
700 IdeDiagnosticProvider *provider)
701 {
702 IdeDiagnosticsGroup *group;
703
704 IDE_ENTRY;
705
706 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
707 g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
708
709 group = g_object_get_data (G_OBJECT (provider), "IDE_DIAGNOSTICS_GROUP");
710
711 ide_diagnostics_group_queue_diagnose (group, self);
712
713 IDE_EXIT;
714 }
715
716 static void
ide_diagnostics_manager_extension_added(IdeExtensionSetAdapter * adapter,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)717 ide_diagnostics_manager_extension_added (IdeExtensionSetAdapter *adapter,
718 PeasPluginInfo *plugin_info,
719 PeasExtension *exten,
720 gpointer user_data)
721 {
722 IdeDiagnosticProvider *provider = (IdeDiagnosticProvider *)exten;
723 IdeDiagnosticsManager *self = user_data;
724 IdeDiagnosticsGroup *group;
725
726 IDE_ENTRY;
727
728 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
729 g_assert (plugin_info != NULL);
730 g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
731 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
732
733 group = ide_diagnostics_manager_find_group_from_adapter (self, adapter);
734
735 /*
736 * We will need access to the group upon completion of the diagnostics,
737 * so we add a reference to the group and allow it to be automatically
738 * cleaned up when the provider finalizes.
739 */
740 g_object_set_data_full (G_OBJECT (provider),
741 "IDE_DIAGNOSTICS_GROUP",
742 ide_diagnostics_group_ref (group),
743 (GDestroyNotify)ide_diagnostics_group_unref);
744
745 /*
746 * We insert a dummy entry into the hashtable upon creation so
747 * that when an async diagnosis completes we can use the presence
748 * of this key to know if we've been unloaded.
749 */
750 g_hash_table_insert (group->diagnostics_by_provider, provider, NULL);
751
752 /*
753 * We need to keep track of when the provider has been invalidated so
754 * that we can queue another request to fetch the diagnostics.
755 */
756 g_signal_connect_object (provider,
757 "invalidated",
758 G_CALLBACK (ide_diagnostics_manager_provider_invalidated),
759 self,
760 G_CONNECT_SWAPPED);
761
762 ide_diagnostic_provider_load (provider);
763
764 ide_diagnostics_group_queue_diagnose (group, self);
765
766 IDE_EXIT;
767 }
768
769 static gboolean
ide_diagnostics_manager_clear_by_provider(IdeDiagnosticsManager * self,IdeDiagnosticProvider * provider)770 ide_diagnostics_manager_clear_by_provider (IdeDiagnosticsManager *self,
771 IdeDiagnosticProvider *provider)
772 {
773 GHashTableIter iter;
774 gpointer value;
775 gboolean changed = FALSE;
776
777 g_assert (IDE_IS_MAIN_THREAD ());
778 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
779 g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
780
781 g_hash_table_iter_init (&iter, self->groups_by_file);
782
783 while (g_hash_table_iter_next (&iter, NULL, &value))
784 {
785 IdeDiagnosticsGroup *group = value;
786
787 g_assert (group != NULL);
788 g_assert (IS_DIAGNOSTICS_GROUP (group));
789
790 if (group->diagnostics_by_provider != NULL)
791 {
792 g_hash_table_remove (group->diagnostics_by_provider, provider);
793
794 /*
795 * If we caused this hashtable to become empty, we can release the
796 * hashtable. The hashtable is guaranteed to not be empty if there
797 * are other providers loaded for this group.
798 */
799 if (g_hash_table_size (group->diagnostics_by_provider) == 0)
800 g_clear_pointer (&group->diagnostics_by_provider, g_hash_table_unref);
801
802 /*
803 * TODO: If this provider is not part of this group, we can possibly
804 * dispose of the group if there are no diagnostics.
805 */
806
807 changed = TRUE;
808 }
809 }
810
811 return changed;
812 }
813
814 static void
ide_diagnostics_manager_extension_removed(IdeExtensionSetAdapter * adapter,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)815 ide_diagnostics_manager_extension_removed (IdeExtensionSetAdapter *adapter,
816 PeasPluginInfo *plugin_info,
817 PeasExtension *exten,
818 gpointer user_data)
819 {
820 IdeDiagnosticProvider *provider = (IdeDiagnosticProvider *)exten;
821 IdeDiagnosticsManager *self = user_data;
822
823 IDE_ENTRY;
824
825 g_assert (IDE_IS_MAIN_THREAD ());
826 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
827 g_assert (plugin_info != NULL);
828 g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
829 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
830
831 g_signal_handlers_disconnect_by_func (provider,
832 G_CALLBACK (ide_diagnostics_manager_provider_invalidated),
833 self);
834
835 /*
836 * The goal of the following is to reomve our diagnostics from any file
837 * that has been loaded. It is possible for diagnostic providers to effect
838 * files outside the buffer they are loaded for and this ensures that we
839 * clean those up.
840 */
841 ide_diagnostics_manager_clear_by_provider (self, provider);
842
843 /* Clear the diagnostics group */
844 g_object_set_data (G_OBJECT (provider), "IDE_DIAGNOSTICS_GROUP", NULL);
845
846 IDE_EXIT;
847 }
848
849 /**
850 * ide_diagnostics_manager_get_busy:
851 *
852 * Gets if the diagnostics manager is currently executing a diagnosis.
853 *
854 * Returns: %TRUE if the #IdeDiagnosticsManager is busy diagnosing.
855 *
856 * Since: 3.32
857 */
858 gboolean
ide_diagnostics_manager_get_busy(IdeDiagnosticsManager * self)859 ide_diagnostics_manager_get_busy (IdeDiagnosticsManager *self)
860 {
861 GHashTableIter iter;
862 gpointer value;
863
864 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
865 g_return_val_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self), FALSE);
866
867 g_hash_table_iter_init (&iter, self->groups_by_file);
868
869 while (g_hash_table_iter_next (&iter, NULL, &value))
870 {
871 IdeDiagnosticsGroup *group = value;
872
873 g_assert (group != NULL);
874 g_assert (IS_DIAGNOSTICS_GROUP (group));
875
876 if (group->in_diagnose > 0)
877 return TRUE;
878 }
879
880 return FALSE;
881 }
882
883 /**
884 * ide_diagnostics_manager_get_diagnostics_for_file:
885 * @self: An #IdeDiagnosticsManager
886 * @file: a #GFile to retrieve diagnostics for
887 *
888 * This function collects all of the diagnostics that have been collected
889 * for @file and returns them as a new #IdeDiagnostics to the caller.
890 *
891 * The #IdeDiagnostics structure will contain zero items if there are
892 * no diagnostics discovered. Therefore, this function will never return
893 * a %NULL value.
894 *
895 * Returns: (transfer full): A new #IdeDiagnostics.
896 *
897 * Since: 3.32
898 */
899 IdeDiagnostics *
ide_diagnostics_manager_get_diagnostics_for_file(IdeDiagnosticsManager * self,GFile * file)900 ide_diagnostics_manager_get_diagnostics_for_file (IdeDiagnosticsManager *self,
901 GFile *file)
902 {
903 g_autoptr(IdeDiagnostics) ret = NULL;
904 IdeDiagnosticsGroup *group;
905
906 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
907 g_return_val_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self), NULL);
908 g_return_val_if_fail (G_IS_FILE (file), NULL);
909
910 ret = ide_diagnostics_new ();
911
912 group = g_hash_table_lookup (self->groups_by_file, file);
913
914 if (group != NULL && group->diagnostics_by_provider != NULL)
915 {
916 GHashTableIter iter;
917 gpointer value;
918
919 g_hash_table_iter_init (&iter, group->diagnostics_by_provider);
920
921 while (g_hash_table_iter_next (&iter, NULL, &value))
922 {
923 IdeDiagnostics *diagnostics = value;
924 guint length;
925
926 if (diagnostics == NULL)
927 continue;
928
929 length = g_list_model_get_n_items (G_LIST_MODEL (diagnostics));
930
931 for (guint i = 0; i < length; i++)
932 {
933 g_autoptr(IdeDiagnostic) diagnostic = NULL;
934
935 diagnostic = g_list_model_get_item (G_LIST_MODEL (diagnostics), i);
936 ide_diagnostics_add (ret, diagnostic);
937 }
938 }
939 }
940
941 return g_steal_pointer (&ret);
942 }
943
944 guint
ide_diagnostics_manager_get_sequence_for_file(IdeDiagnosticsManager * self,GFile * file)945 ide_diagnostics_manager_get_sequence_for_file (IdeDiagnosticsManager *self,
946 GFile *file)
947 {
948 IdeDiagnosticsGroup *group;
949
950 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), 0);
951 g_return_val_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self), 0);
952 g_return_val_if_fail (G_IS_FILE (file), 0);
953
954 group = g_hash_table_lookup (self->groups_by_file, file);
955
956 if (group != NULL)
957 {
958 g_assert (IS_DIAGNOSTICS_GROUP (group));
959 g_assert (G_IS_FILE (group->file));
960 g_assert (g_file_equal (group->file, file));
961
962 return group->sequence;
963 }
964
965 return 0;
966 }
967
968 /**
969 * ide_diagnostics_manager_rediagnose:
970 * @self: an #IdeDiagnosticsManager
971 * @buffer: an #IdeBuffer
972 *
973 * Requests that the diagnostics be reloaded for @buffer.
974 *
975 * You may want to call this if you changed something that a buffer depends on,
976 * and want to seamlessly update its diagnostics with that updated information.
977 *
978 * Since: 3.32
979 */
980 void
ide_diagnostics_manager_rediagnose(IdeDiagnosticsManager * self,IdeBuffer * buffer)981 ide_diagnostics_manager_rediagnose (IdeDiagnosticsManager *self,
982 IdeBuffer *buffer)
983 {
984 IdeDiagnosticsGroup *group;
985 GFile *file;
986
987 g_return_if_fail (IDE_IS_MAIN_THREAD ());
988 g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
989 g_return_if_fail (IDE_IS_BUFFER (buffer));
990
991 file = ide_buffer_get_file (buffer);
992 group = ide_diagnostics_manager_find_group (self, file);
993
994 ide_diagnostics_group_queue_diagnose (group, self);
995 }
996
997 /**
998 * ide_diagnostics_manager_from_context:
999 * @context: an #IdeContext
1000 *
1001 * Gets the diagnostics manager for the context.
1002 *
1003 * Returns: (transfer none): an #IdeDiagnosticsManager
1004 *
1005 * Since: 3.32
1006 */
1007 IdeDiagnosticsManager *
ide_diagnostics_manager_from_context(IdeContext * context)1008 ide_diagnostics_manager_from_context (IdeContext *context)
1009 {
1010 IdeDiagnosticsManager *self;
1011
1012 g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
1013
1014 ide_object_lock (IDE_OBJECT (context));
1015 if (!(self = ide_context_peek_child_typed (context, IDE_TYPE_DIAGNOSTICS_MANAGER)))
1016 {
1017 g_autoptr(IdeDiagnosticsManager) created = NULL;
1018 created = ide_object_ensure_child_typed (IDE_OBJECT (context),
1019 IDE_TYPE_DIAGNOSTICS_MANAGER);
1020 self = ide_context_peek_child_typed (context, IDE_TYPE_DIAGNOSTICS_MANAGER);
1021 }
1022 ide_object_unlock (IDE_OBJECT (context));
1023
1024 return self;
1025 }
1026
1027 void
_ide_diagnostics_manager_file_closed(IdeDiagnosticsManager * self,GFile * file)1028 _ide_diagnostics_manager_file_closed (IdeDiagnosticsManager *self,
1029 GFile *file)
1030 {
1031 IdeDiagnosticsGroup *group;
1032 gboolean has_diagnostics;
1033
1034 IDE_ENTRY;
1035
1036 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1037 g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
1038 g_return_if_fail (G_IS_FILE (file));
1039
1040 /*
1041 * The goal here is to cleanup everything we can about this group that
1042 * is part of a loaded buffer. We might want to keep the group around
1043 * in case it is useful from other providers.
1044 */
1045
1046 group = ide_diagnostics_manager_find_group (self, file);
1047
1048 g_assert (group != NULL);
1049 g_assert (IS_DIAGNOSTICS_GROUP (group));
1050
1051 /* Clear some state we've been tracking */
1052 g_clear_pointer (&group->contents, g_bytes_unref);
1053 group->lang_id = NULL;
1054 group->needs_diagnose = FALSE;
1055
1056 /*
1057 * We track if we have diagnostics now so that after we unload the
1058 * the providers, we can save that bit for later.
1059 */
1060 has_diagnostics = ide_diagnostics_group_has_diagnostics (group);
1061
1062 /*
1063 * Force our diagnostic providers to unload. This will cause them
1064 * extension-removed signal to be called for each provider which
1065 * in turn will perform per-provider cleanup including the removal
1066 * of its diagnostics from all groups. (A provider can in practice
1067 * affect another group since a .c file could create a diagnostic
1068 * for a .h).
1069 */
1070 ide_clear_and_destroy_object (&group->adapter);
1071
1072 /*
1073 * Even after unloading the diagnostic providers, we might still have
1074 * diagnostics that were created from other files (this could happen when
1075 * one diagnostic is created for a header from a source file). So we don't
1076 * want to wipe out the hashtable unless everything was unloaded. The other
1077 * provider will cleanup during its own destruction.
1078 */
1079 if (group->diagnostics_by_provider != NULL &&
1080 g_hash_table_size (group->diagnostics_by_provider) == 0)
1081 g_clear_pointer (&group->diagnostics_by_provider, g_hash_table_unref);
1082
1083 group->has_diagnostics = has_diagnostics;
1084
1085 IDE_EXIT;
1086 }
1087
1088 void
_ide_diagnostics_manager_file_changed(IdeDiagnosticsManager * self,GFile * file,GBytes * contents,const gchar * lang_id)1089 _ide_diagnostics_manager_file_changed (IdeDiagnosticsManager *self,
1090 GFile *file,
1091 GBytes *contents,
1092 const gchar *lang_id)
1093 {
1094 IdeDiagnosticsGroup *group;
1095
1096 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1097 g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
1098 g_return_if_fail (G_IS_FILE (file));
1099
1100 group = ide_diagnostics_manager_find_group (self, file);
1101
1102 g_clear_pointer (&group->contents, g_bytes_unref);
1103
1104 group->lang_id = g_intern_string (lang_id);
1105 group->contents = contents ? g_bytes_ref (contents) : NULL;
1106
1107 ide_diagnostics_group_queue_diagnose (group, self);
1108 }
1109
1110 void
_ide_diagnostics_manager_language_changed(IdeDiagnosticsManager * self,GFile * file,const gchar * lang_id)1111 _ide_diagnostics_manager_language_changed (IdeDiagnosticsManager *self,
1112 GFile *file,
1113 const gchar *lang_id)
1114 {
1115 IdeDiagnosticsGroup *group;
1116
1117 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1118 g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
1119
1120 group = ide_diagnostics_manager_find_group (self, file);
1121 group->lang_id = g_intern_string (lang_id);
1122
1123 if (group->adapter != NULL)
1124 ide_extension_set_adapter_set_value (group->adapter, lang_id);
1125
1126 ide_diagnostics_group_queue_diagnose (group, self);
1127 }
1128
1129 void
_ide_diagnostics_manager_file_opened(IdeDiagnosticsManager * self,GFile * file,const gchar * lang_id)1130 _ide_diagnostics_manager_file_opened (IdeDiagnosticsManager *self,
1131 GFile *file,
1132 const gchar *lang_id)
1133 {
1134 IdeDiagnosticsGroup *group;
1135
1136 IDE_ENTRY;
1137
1138 g_assert (IDE_IS_MAIN_THREAD ());
1139 g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
1140 g_assert (G_IS_FILE (file));
1141
1142 group = ide_diagnostics_manager_find_group (self, file);
1143
1144 if (group->diagnostics_by_provider == NULL)
1145 group->diagnostics_by_provider =
1146 g_hash_table_new_full (NULL, NULL, NULL, free_diagnostics);
1147
1148 group->lang_id = g_intern_string (lang_id);
1149
1150 if (group->adapter == NULL)
1151 {
1152 group->adapter = ide_extension_set_adapter_new (IDE_OBJECT (self),
1153 peas_engine_get_default (),
1154 IDE_TYPE_DIAGNOSTIC_PROVIDER,
1155 "Diagnostic-Provider-Languages",
1156 lang_id);
1157
1158 g_signal_connect_object (group->adapter,
1159 "extension-added",
1160 G_CALLBACK (ide_diagnostics_manager_extension_added),
1161 self,
1162 0);
1163
1164 g_signal_connect_object (group->adapter,
1165 "extension-removed",
1166 G_CALLBACK (ide_diagnostics_manager_extension_removed),
1167 self,
1168 0);
1169
1170 ide_extension_set_adapter_foreach (group->adapter,
1171 ide_diagnostics_manager_extension_added,
1172 self);
1173 }
1174
1175 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (group->adapter));
1176 g_assert (g_hash_table_lookup (self->groups_by_file, file) == group);
1177
1178 ide_diagnostics_group_queue_diagnose (group, self);
1179
1180 IDE_EXIT;
1181 }
1182
1183