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