1 /* ide-diagnostics.c
2  *
3  * Copyright 2015-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"
22 
23 #include "config.h"
24 
25 #include "ide-diagnostic.h"
26 #include "ide-diagnostics.h"
27 #include "ide-location.h"
28 
29 typedef struct
30 {
31   GPtrArray  *items;
32   GHashTable *caches;
33   guint       n_warnings;
34   guint       n_errors;
35 } IdeDiagnosticsPrivate;
36 
37 typedef struct
38 {
39   gint                  line : 28;
40   IdeDiagnosticSeverity severity : 4;
41 } IdeDiagnosticsCacheLine;
42 
43 typedef struct
44 {
45   GFile  *file;
46   GArray *lines;
47 } IdeDiagnosticsCache;
48 
49 typedef struct
50 {
51   guint begin;
52   guint end;
53 } LookupKey;
54 
55 enum {
56   PROP_0,
57   PROP_HAS_ERRORS,
58   PROP_HAS_WARNINGS,
59   PROP_N_ERRORS,
60   PROP_N_WARNINGS,
61   N_PROPS
62 };
63 
64 static void list_model_iface_init (GListModelInterface *iface);
65 
66 G_DEFINE_TYPE_WITH_CODE (IdeDiagnostics, ide_diagnostics, IDE_TYPE_OBJECT,
67                          G_ADD_PRIVATE (IdeDiagnostics)
68                          G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
69 
70 static GParamSpec *properties [N_PROPS];
71 
72 static void
ide_diagnostics_cache_free(gpointer data)73 ide_diagnostics_cache_free (gpointer data)
74 {
75   IdeDiagnosticsCache *cache = data;
76 
77   g_clear_object (&cache->file);
78   g_clear_pointer (&cache->lines, g_array_unref);
79   g_slice_free (IdeDiagnosticsCache, cache);
80 }
81 
82 static void
ide_diagnostics_finalize(GObject * object)83 ide_diagnostics_finalize (GObject *object)
84 {
85   IdeDiagnostics *self = (IdeDiagnostics *)object;
86   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
87 
88   g_clear_pointer (&priv->items, g_ptr_array_unref);
89   g_clear_pointer (&priv->caches, g_hash_table_unref);
90 
91   G_OBJECT_CLASS (ide_diagnostics_parent_class)->finalize (object);
92 }
93 
94 static void
ide_diagnostics_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)95 ide_diagnostics_get_property (GObject    *object,
96                               guint       prop_id,
97                               GValue     *value,
98                               GParamSpec *pspec)
99 {
100   IdeDiagnostics *self = IDE_DIAGNOSTICS (object);
101 
102   switch (prop_id)
103     {
104     case PROP_HAS_WARNINGS:
105       g_value_set_boolean (value, ide_diagnostics_get_has_warnings (self));
106       break;
107 
108     case PROP_HAS_ERRORS:
109       g_value_set_boolean (value, ide_diagnostics_get_has_errors (self));
110       break;
111 
112     case PROP_N_ERRORS:
113       g_value_set_uint (value, ide_diagnostics_get_n_errors (self));
114       break;
115 
116     case PROP_N_WARNINGS:
117       g_value_set_uint (value, ide_diagnostics_get_n_warnings (self));
118       break;
119 
120     default:
121       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
122     }
123 }
124 
125 static void
ide_diagnostics_class_init(IdeDiagnosticsClass * klass)126 ide_diagnostics_class_init (IdeDiagnosticsClass *klass)
127 {
128   GObjectClass *object_class = G_OBJECT_CLASS (klass);
129 
130   object_class->finalize = ide_diagnostics_finalize;
131   object_class->get_property = ide_diagnostics_get_property;
132 
133   properties [PROP_HAS_WARNINGS] =
134     g_param_spec_boolean ("has-warnings",
135                          "Has Warnings",
136                          "If there are any warnings in the diagnostic set",
137                          FALSE,
138                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
139 
140   properties [PROP_HAS_ERRORS] =
141     g_param_spec_boolean ("has-errors",
142                          "Has Errors",
143                          "If there are any errors in the diagnostic set",
144                          FALSE,
145                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
146 
147   properties [PROP_N_WARNINGS] =
148     g_param_spec_uint ("n-warnings",
149                        "N Warnings",
150                        "Number of warnings in diagnostic set",
151                        0, G_MAXUINT, 0,
152                        (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
153 
154   properties [PROP_N_ERRORS] =
155     g_param_spec_uint ("n-errors",
156                        "N Errors",
157                        "Number of errors in diagnostic set",
158                        0, G_MAXUINT, 0,
159                        (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
160 
161   g_object_class_install_properties (object_class, N_PROPS, properties);
162 }
163 
164 static void
ide_diagnostics_init(IdeDiagnostics * self)165 ide_diagnostics_init (IdeDiagnostics *self)
166 {
167   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
168 
169   priv->items = g_ptr_array_new_with_free_func (g_object_unref);
170 }
171 
172 IdeDiagnostics *
ide_diagnostics_new(void)173 ide_diagnostics_new (void)
174 {
175   return g_object_new (IDE_TYPE_DIAGNOSTICS, NULL);
176 }
177 
178 void
ide_diagnostics_add(IdeDiagnostics * self,IdeDiagnostic * diagnostic)179 ide_diagnostics_add (IdeDiagnostics *self,
180                      IdeDiagnostic  *diagnostic)
181 {
182   g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
183   g_return_if_fail (IDE_IS_DIAGNOSTIC (diagnostic));
184 
185   ide_diagnostics_take (self, g_object_ref (diagnostic));
186 }
187 
188 void
ide_diagnostics_take(IdeDiagnostics * self,IdeDiagnostic * diagnostic)189 ide_diagnostics_take (IdeDiagnostics *self,
190                       IdeDiagnostic  *diagnostic)
191 {
192   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
193   IdeDiagnosticSeverity severity;
194   guint position;
195 
196   g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
197   g_return_if_fail (IDE_IS_DIAGNOSTIC (diagnostic));
198 
199   severity = ide_diagnostic_get_severity (diagnostic);
200 
201   position = priv->items->len;
202   g_ptr_array_add (priv->items, g_steal_pointer (&diagnostic));
203   g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
204 
205   switch (severity)
206     {
207     case IDE_DIAGNOSTIC_ERROR:
208     case IDE_DIAGNOSTIC_FATAL:
209       priv->n_errors++;
210       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_ERRORS]);
211       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_ERRORS]);
212       break;
213 
214     case IDE_DIAGNOSTIC_WARNING:
215     case IDE_DIAGNOSTIC_DEPRECATED:
216     case IDE_DIAGNOSTIC_UNUSED:
217       priv->n_warnings++;
218       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_WARNINGS]);
219       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_WARNINGS]);
220       break;
221 
222     case IDE_DIAGNOSTIC_IGNORED:
223     case IDE_DIAGNOSTIC_NOTE:
224     default:
225       break;
226     }
227 }
228 
229 void
ide_diagnostics_merge(IdeDiagnostics * self,IdeDiagnostics * other)230 ide_diagnostics_merge (IdeDiagnostics *self,
231                        IdeDiagnostics *other)
232 {
233   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
234   IdeDiagnosticsPrivate *other_priv = ide_diagnostics_get_instance_private (other);
235   guint position;
236 
237   g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
238   g_return_if_fail (IDE_IS_DIAGNOSTICS (other));
239 
240   position = priv->items->len;
241 
242   for (guint i = 0; i < other_priv->items->len; i++)
243     {
244       IdeDiagnostic *diagnostic = g_ptr_array_index (other_priv->items, i);
245       ide_diagnostics_take (self, g_object_ref (diagnostic));
246     }
247 
248   g_list_model_items_changed (G_LIST_MODEL (self), position, 0, other_priv->items->len);
249 }
250 
251 gboolean
ide_diagnostics_get_has_errors(IdeDiagnostics * self)252 ide_diagnostics_get_has_errors (IdeDiagnostics *self)
253 {
254   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
255 
256   g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), FALSE);
257 
258   return priv->n_errors > 0;
259 }
260 
261 guint
ide_diagnostics_get_n_errors(IdeDiagnostics * self)262 ide_diagnostics_get_n_errors (IdeDiagnostics *self)
263 {
264   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
265 
266   g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), 0);
267 
268   return priv->n_errors;
269 }
270 
271 gboolean
ide_diagnostics_get_has_warnings(IdeDiagnostics * self)272 ide_diagnostics_get_has_warnings (IdeDiagnostics *self)
273 {
274   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
275 
276   g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), FALSE);
277 
278   return priv->n_warnings > 0;
279 }
280 
281 guint
ide_diagnostics_get_n_warnings(IdeDiagnostics * self)282 ide_diagnostics_get_n_warnings (IdeDiagnostics *self)
283 {
284   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
285 
286   g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), 0);
287 
288   return priv->n_warnings;
289 }
290 
291 static GType
ide_diagnostics_get_item_type(GListModel * model)292 ide_diagnostics_get_item_type (GListModel *model)
293 {
294   return IDE_TYPE_DIAGNOSTIC;
295 }
296 
297 static guint
ide_diagnostics_get_n_items(GListModel * model)298 ide_diagnostics_get_n_items (GListModel *model)
299 {
300   IdeDiagnostics *self = (IdeDiagnostics *)model;
301   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
302 
303   g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), 0);
304 
305   return priv->items->len;
306 }
307 
308 static gpointer
ide_diagnostics_get_item(GListModel * model,guint position)309 ide_diagnostics_get_item (GListModel *model,
310                           guint       position)
311 {
312   IdeDiagnostics *self = (IdeDiagnostics *)model;
313   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
314 
315   g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), NULL);
316 
317   if (position < priv->items->len)
318     return g_object_ref (g_ptr_array_index (priv->items, position));
319 
320   return NULL;
321 }
322 
323 static void
list_model_iface_init(GListModelInterface * iface)324 list_model_iface_init (GListModelInterface *iface)
325 {
326   iface->get_n_items = ide_diagnostics_get_n_items;
327   iface->get_item_type = ide_diagnostics_get_item_type;
328   iface->get_item = ide_diagnostics_get_item;
329 }
330 
331 static gint
compare_lines(gconstpointer a,gconstpointer b)332 compare_lines (gconstpointer a,
333                gconstpointer b)
334 {
335   const IdeDiagnosticsCacheLine *line_a = a;
336   const IdeDiagnosticsCacheLine *line_b = b;
337 
338   return line_a->line - line_b->line;
339 }
340 
341 static void
ide_diagnostics_build_caches(IdeDiagnostics * self)342 ide_diagnostics_build_caches (IdeDiagnostics *self)
343 {
344   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
345   g_autoptr(GHashTable) caches = NULL;
346   IdeDiagnosticsCache *cache;
347   GHashTableIter iter;
348   GFile *file;
349 
350   g_assert (IDE_IS_DIAGNOSTICS (self));
351   g_assert (priv->caches == NULL);
352 
353   caches = g_hash_table_new_full (g_file_hash,
354                                   (GEqualFunc)g_file_equal,
355                                   g_object_unref,
356                                   ide_diagnostics_cache_free);
357 
358   for (guint i = 0; i < priv->items->len; i++)
359     {
360       IdeDiagnostic *diag = g_ptr_array_index (priv->items, i);
361       IdeDiagnosticsCacheLine val;
362       IdeLocation *location;
363 
364       if (!(file = ide_diagnostic_get_file (diag)))
365         continue;
366 
367       if (!(location = ide_diagnostic_get_location (diag)))
368         continue;
369 
370       if (!(cache = g_hash_table_lookup (caches, file)))
371         {
372           cache = g_slice_new0 (IdeDiagnosticsCache);
373           cache->file = g_object_ref (file);
374           cache->lines = g_array_new (FALSE, FALSE, sizeof (IdeDiagnosticsCacheLine));
375           g_hash_table_insert (caches, g_object_ref (file), cache);
376         }
377 
378       val.severity = ide_diagnostic_get_severity (diag);
379       val.line = ide_location_get_line (location);
380 
381       g_array_append_val (cache->lines, val);
382     }
383 
384   g_hash_table_iter_init (&iter, caches);
385 
386   while (g_hash_table_iter_next (&iter, (gpointer *)&file, (gpointer *)&cache))
387     g_array_sort (cache->lines, compare_lines);
388 
389   priv->caches = g_steal_pointer (&caches);
390 }
391 
392 /**
393  * ide_diagnostics_foreach_line_in_range:
394  * @self: an #IdeDiagnostics
395  * @file: a #GFile
396  * @begin_line: the starting line
397  * @end_line: the ending line
398  * @callback: (scope call): a callback to execute for each matching line
399  * @user_data: user data for @callback
400  *
401  * This function calls @callback for every line with diagnostics between
402  * @begin_line and @end_line. This is useful when drawing information about
403  * diagnostics in an editor where a known number of lines are visible.
404  *
405  * Since: 3.32
406  */
407 void
ide_diagnostics_foreach_line_in_range(IdeDiagnostics * self,GFile * file,guint begin_line,guint end_line,IdeDiagnosticsLineCallback callback,gpointer user_data)408 ide_diagnostics_foreach_line_in_range (IdeDiagnostics             *self,
409                                        GFile                      *file,
410                                        guint                       begin_line,
411                                        guint                       end_line,
412                                        IdeDiagnosticsLineCallback  callback,
413                                        gpointer                    user_data)
414 {
415   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
416   const IdeDiagnosticsCache *cache;
417 
418   g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
419   g_return_if_fail (G_IS_FILE (file));
420 
421   if (priv->items->len == 0)
422     return;
423 
424   if (priv->caches == NULL)
425     ide_diagnostics_build_caches (self);
426 
427   if (!(cache = g_hash_table_lookup (priv->caches, file)))
428     return;
429 
430   for (guint i = 0; i < cache->lines->len; i++)
431     {
432       const IdeDiagnosticsCacheLine *line = &g_array_index (cache->lines, IdeDiagnosticsCacheLine, i);
433 
434       if (line->line < begin_line)
435         continue;
436 
437       if (line->line > end_line)
438         break;
439 
440       callback (line->line, line->severity, user_data);
441     }
442 }
443 
444 /**
445  * ide_diagnostics_get_diagnostic_at_line:
446  * @self: a #IdeDiagnostics
447  * @file: the target file
448  * @line: a line number
449  *
450  * Locates an #IdeDiagnostic in @file at @line.
451  *
452  * Returns: (transfer none) (nullable): an #IdeDiagnostic or %NULL
453  *
454  * Since: 3.32
455  */
456 IdeDiagnostic *
ide_diagnostics_get_diagnostic_at_line(IdeDiagnostics * self,GFile * file,guint line)457 ide_diagnostics_get_diagnostic_at_line (IdeDiagnostics *self,
458                                         GFile          *file,
459                                         guint           line)
460 {
461   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
462 
463   g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), NULL);
464   g_return_val_if_fail (G_IS_FILE (file), NULL);
465 
466   for (guint i = 0; i < priv->items->len; i++)
467     {
468       IdeDiagnostic *diag = g_ptr_array_index (priv->items, i);
469       IdeLocation *loc = ide_diagnostic_get_location (diag);
470       GFile *loc_file;
471       guint loc_line;
472 
473       if (loc == NULL)
474         continue;
475 
476       loc_file = ide_location_get_file (loc);
477       loc_line = ide_location_get_line (loc);
478 
479       if (loc_line == line && g_file_equal (file, loc_file))
480         return diag;
481     }
482 
483   return NULL;
484 }
485 
486 /**
487  * ide_diagnostics_get_diagnostics_at_line:
488  * @self: a #IdeDiagnostics
489  * @file: the target file
490  * @line: a line number
491  *
492  * Locates all #IdeDiagnostic in @file at @line.
493  *
494  * Returns: (transfer full) (element-type IdeDiagnostic) (nullable): an #GPtrArray or %NULL
495  *
496  * Since: 3.38
497  */
498 GPtrArray *
ide_diagnostics_get_diagnostics_at_line(IdeDiagnostics * self,GFile * file,guint line)499 ide_diagnostics_get_diagnostics_at_line (IdeDiagnostics *self,
500                                          GFile          *file,
501                                          guint           line)
502 {
503   IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
504   g_autoptr(GPtrArray) valid_diag = NULL;
505 
506   g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), NULL);
507   g_return_val_if_fail (G_IS_FILE (file), NULL);
508 
509   valid_diag = g_ptr_array_new_with_free_func (g_object_unref);
510 
511   for (guint i = 0; i < priv->items->len; i++)
512     {
513       IdeDiagnostic *diag = g_ptr_array_index (priv->items, i);
514       IdeLocation *loc = ide_diagnostic_get_location (diag);
515       GFile *loc_file;
516       guint loc_line;
517 
518       if (loc == NULL)
519         continue;
520 
521       loc_file = ide_location_get_file (loc);
522       loc_line = ide_location_get_line (loc);
523 
524       if (loc_line == line && g_file_equal (file, loc_file))
525         g_ptr_array_add (valid_diag, g_object_ref(diag));
526     }
527 
528   if (valid_diag->len != 0)
529     return IDE_PTR_ARRAY_STEAL_FULL (&valid_diag);
530 
531   return NULL;
532 }
533 
534 /**
535  * ide_diagnostics_new_from_array:
536  * @array: (nullable) (element-type IdeDiagnostic): optional array
537  *   of diagnostics to add.
538  *
539  * Returns: (transfer full): an #IdeDiagnostics
540  *
541  * Since: 3.32
542  */
543 IdeDiagnostics *
ide_diagnostics_new_from_array(GPtrArray * array)544 ide_diagnostics_new_from_array (GPtrArray *array)
545 {
546   IdeDiagnostics *ret = ide_diagnostics_new ();
547 
548   if (array != NULL)
549     {
550       for (guint i = 0; i < array->len; i++)
551         ide_diagnostics_add (ret, g_ptr_array_index (array, i));
552     }
553 
554   return g_steal_pointer (&ret);
555 }
556